blob: 0c0da770e6f07ab5a9c04b2bdc542801e4b46f31 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Doug Zongkerb34fcce2014-09-11 09:34:56 -070033from rangelib import *
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Doug Zongker55d93282011-01-25 17:03:34 -080035try:
davidcad0bb92011-03-15 14:21:38 +000036 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037except ImportError:
davidcad0bb92011-03-15 14:21:38 +000038 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080039
Doug Zongkereef39442009-04-02 12:14:19 -070040# missing in Python 2.4 and before
41if not hasattr(os, "SEEK_SET"):
42 os.SEEK_SET = 0
43
44class Options(object): pass
45OPTIONS = Options()
Doug Zongker85448772014-09-09 14:59:20 -070046
47DEFAULT_SEARCH_PATH_BY_PLATFORM = {
48 "linux2": "out/host/linux-x86",
49 "darwin": "out/host/darwin-x86",
50 }
51OPTIONS.search_path = DEFAULT_SEARCH_PATH_BY_PLATFORM.get(sys.platform, None)
52
T.R. Fullhart37e10522013-03-18 10:31:26 -070053OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
54OPTIONS.extra_signapk_args = []
55OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070056OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070057OPTIONS.public_key_suffix = ".x509.pem"
58OPTIONS.private_key_suffix = ".pk8"
Baligh Uddine2048682014-11-20 09:52:05 -080059OPTIONS.boot_signer_path = "boot_signer" # use otatools built boot_signer by default
Doug Zongkereef39442009-04-02 12:14:19 -070060OPTIONS.verbose = False
61OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070062OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080063OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070064OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070065
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080066
67# Values for "certificate" in apkcerts that mean special things.
68SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
69
70
Doug Zongkereef39442009-04-02 12:14:19 -070071class ExternalError(RuntimeError): pass
72
73
74def Run(args, **kwargs):
75 """Create and return a subprocess.Popen object, printing the command
76 line on the terminal if -v was specified."""
77 if OPTIONS.verbose:
78 print " running: ", " ".join(args)
79 return subprocess.Popen(args, **kwargs)
80
81
Ying Wang7e6d4e42010-12-13 16:25:36 -080082def CloseInheritedPipes():
83 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
84 before doing other work."""
85 if platform.system() != "Darwin":
86 return
87 for d in range(3, 1025):
88 try:
89 stat = os.fstat(d)
90 if stat is not None:
91 pipebit = stat[0] & 0x1000
92 if pipebit != 0:
93 os.close(d)
94 except OSError:
95 pass
96
97
Doug Zongkerc9253822014-02-04 12:17:58 -080098def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070099 """Read and parse the META/misc_info.txt key/value pairs from the
100 input target files and return a dict."""
101
Doug Zongkerc9253822014-02-04 12:17:58 -0800102 def read_helper(fn):
103 if isinstance(input, zipfile.ZipFile):
104 return input.read(fn)
105 else:
106 path = os.path.join(input, *fn.split("/"))
107 try:
108 with open(path) as f:
109 return f.read()
110 except IOError, e:
111 if e.errno == errno.ENOENT:
112 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700113 d = {}
114 try:
Michael Runge6e836112014-04-15 17:40:21 -0700115 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700116 except KeyError:
117 # ok if misc_info.txt doesn't exist
118 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700119
Doug Zongker37974732010-09-16 17:44:38 -0700120 # backwards compatibility: These values used to be in their own
121 # files. Look for them, in case we're processing an old
122 # target_files zip.
123
124 if "mkyaffs2_extra_flags" not in d:
125 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800126 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800133 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700134 except KeyError:
135 raise ValueError("can't find recovery API version in input target-files")
136
137 if "tool_extensions" not in d:
138 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800139 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700140 except KeyError:
141 # ok if extensions don't exist
142 pass
143
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800144 if "fstab_version" not in d:
145 d["fstab_version"] = "1"
146
Doug Zongker37974732010-09-16 17:44:38 -0700147 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700149 for line in data.split("\n"):
150 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700151 name, value = line.split(" ", 1)
152 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700153 if name == "blocksize":
154 d[name] = value
155 else:
156 d[name + "_size"] = value
157 except KeyError:
158 pass
159
160 def makeint(key):
161 if key in d:
162 d[key] = int(d[key], 0)
163
164 makeint("recovery_api_version")
165 makeint("blocksize")
166 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700167 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700168 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700169 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700170 makeint("recovery_size")
171 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800172 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700173
Doug Zongkerc9253822014-02-04 12:17:58 -0800174 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
175 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700176 return d
177
Doug Zongkerc9253822014-02-04 12:17:58 -0800178def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700181 except KeyError:
182 print "Warning: could not find SYSTEM/build.prop in %s" % zip
183 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700184 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185
Michael Runge6e836112014-04-15 17:40:21 -0700186def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700188 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700189 line = line.strip()
190 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700191 if "=" in line:
192 name, value = line.split("=", 1)
193 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700194 return d
195
Doug Zongkerc9253822014-02-04 12:17:58 -0800196def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700197 class Partition(object):
198 pass
199
200 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800201 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700202 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800203 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700204 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700205
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800206 if fstab_version == 1:
207 d = {}
208 for line in data.split("\n"):
209 line = line.strip()
210 if not line or line.startswith("#"): continue
211 pieces = line.split()
212 if not (3 <= len(pieces) <= 4):
213 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800215 p = Partition()
216 p.mount_point = pieces[0]
217 p.fs_type = pieces[1]
218 p.device = pieces[2]
219 p.length = 0
220 options = None
221 if len(pieces) >= 4:
222 if pieces[3].startswith("/"):
223 p.device2 = pieces[3]
224 if len(pieces) >= 5:
225 options = pieces[4]
226 else:
227 p.device2 = None
228 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800229 else:
230 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700231
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 if options:
233 options = options.split(",")
234 for i in options:
235 if i.startswith("length="):
236 p.length = int(i[7:])
237 else:
238 print "%s: unknown option \"%s\"" % (p.mount_point, i)
239
240 d[p.mount_point] = p
241
242 elif fstab_version == 2:
243 d = {}
244 for line in data.split("\n"):
245 line = line.strip()
246 if not line or line.startswith("#"): continue
247 pieces = line.split()
248 if len(pieces) != 5:
249 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
250
251 # Ignore entries that are managed by vold
252 options = pieces[4]
253 if "voldmanaged=" in options: continue
254
255 # It's a good line, parse it
256 p = Partition()
257 p.device = pieces[0]
258 p.mount_point = pieces[1]
259 p.fs_type = pieces[2]
260 p.device2 = None
261 p.length = 0
262
Doug Zongker086cbb02011-02-17 15:54:20 -0800263 options = options.split(",")
264 for i in options:
265 if i.startswith("length="):
266 p.length = int(i[7:])
267 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800268 # Ignore all unknown options in the unified fstab
269 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800270
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271 d[p.mount_point] = p
272
273 else:
274 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
275
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700276 return d
277
278
Doug Zongker37974732010-09-16 17:44:38 -0700279def DumpInfoDict(d):
280 for k, v in sorted(d.items()):
281 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700282
Doug Zongkerd5131602012-08-02 14:46:42 -0700283def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700284 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700285 'sourcedir'), and turn them into a boot image. Return the image
286 data, or None if sourcedir does not appear to contains files for
287 building the requested image."""
288
289 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
290 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
291 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700292
Doug Zongkerd5131602012-08-02 14:46:42 -0700293 if info_dict is None:
294 info_dict = OPTIONS.info_dict
295
Doug Zongkereef39442009-04-02 12:14:19 -0700296 ramdisk_img = tempfile.NamedTemporaryFile()
297 img = tempfile.NamedTemporaryFile()
298
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700299 if os.access(fs_config_file, os.F_OK):
300 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
301 else:
302 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
303 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700304 p2 = Run(["minigzip"],
305 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700306
307 p2.wait()
308 p1.wait()
309 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700310 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700311
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800312 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
313 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
314
315 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700316
Benoit Fradina45a8682014-07-14 21:00:43 +0200317 fn = os.path.join(sourcedir, "second")
318 if os.access(fn, os.F_OK):
319 cmd.append("--second")
320 cmd.append(fn)
321
Doug Zongker171f1cd2009-06-15 22:36:37 -0700322 fn = os.path.join(sourcedir, "cmdline")
323 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700324 cmd.append("--cmdline")
325 cmd.append(open(fn).read().rstrip("\n"))
326
327 fn = os.path.join(sourcedir, "base")
328 if os.access(fn, os.F_OK):
329 cmd.append("--base")
330 cmd.append(open(fn).read().rstrip("\n"))
331
Ying Wang4de6b5b2010-08-25 14:29:34 -0700332 fn = os.path.join(sourcedir, "pagesize")
333 if os.access(fn, os.F_OK):
334 cmd.append("--pagesize")
335 cmd.append(open(fn).read().rstrip("\n"))
336
Doug Zongkerd5131602012-08-02 14:46:42 -0700337 args = info_dict.get("mkbootimg_args", None)
338 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700339 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700340
Doug Zongker38a649f2009-06-17 09:07:09 -0700341 cmd.extend(["--ramdisk", ramdisk_img.name,
342 "--output", img.name])
343
344 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700345 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700346 assert p.returncode == 0, "mkbootimg of %s image failed" % (
347 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700348
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700349 if info_dict.get("verity_key", None):
350 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddine2048682014-11-20 09:52:05 -0800351 cmd = [OPTIONS.boot_signer_path, path, img.name, info_dict["verity_key"] + ".pk8", info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700352 p = Run(cmd, stdout=subprocess.PIPE)
353 p.communicate()
354 assert p.returncode == 0, "boot_signer of %s image failed" % path
355
Doug Zongkereef39442009-04-02 12:14:19 -0700356 img.seek(os.SEEK_SET, 0)
357 data = img.read()
358
359 ramdisk_img.close()
360 img.close()
361
362 return data
363
364
Doug Zongkerd5131602012-08-02 14:46:42 -0700365def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
366 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800367 """Return a File object (with name 'name') with the desired bootable
368 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700369 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
370 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800371 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700372
Doug Zongker55d93282011-01-25 17:03:34 -0800373 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
374 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700375 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800376 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700377
378 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
379 if os.path.exists(prebuilt_path):
380 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
381 return File.FromLocalFile(name, prebuilt_path)
382
383 print "building image from target_files %s..." % (tree_subdir,)
384 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
385 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
386 os.path.join(unpack_dir, fs_config),
387 info_dict)
388 if data:
389 return File(name, data)
390 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800391
Doug Zongkereef39442009-04-02 12:14:19 -0700392
Doug Zongker75f17362009-12-08 13:46:44 -0800393def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800394 """Unzip the given archive into a temporary directory and return the name.
395
396 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
397 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
398
399 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
400 main file), open for reading.
401 """
Doug Zongkereef39442009-04-02 12:14:19 -0700402
403 tmp = tempfile.mkdtemp(prefix="targetfiles-")
404 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800405
406 def unzip_to_dir(filename, dirname):
407 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
408 if pattern is not None:
409 cmd.append(pattern)
410 p = Run(cmd, stdout=subprocess.PIPE)
411 p.communicate()
412 if p.returncode != 0:
413 raise ExternalError("failed to unzip input target-files \"%s\"" %
414 (filename,))
415
416 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
417 if m:
418 unzip_to_dir(m.group(1), tmp)
419 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
420 filename = m.group(1)
421 else:
422 unzip_to_dir(filename, tmp)
423
424 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700425
426
427def GetKeyPasswords(keylist):
428 """Given a list of keys, prompt the user to enter passwords for
429 those which require them. Return a {key: password} dict. password
430 will be None if the key has no password."""
431
Doug Zongker8ce7c252009-05-22 13:34:54 -0700432 no_passwords = []
433 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700434 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700435 devnull = open("/dev/null", "w+b")
436 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800437 # We don't need a password for things that aren't really keys.
438 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700439 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700440 continue
441
T.R. Fullhart37e10522013-03-18 10:31:26 -0700442 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700443 "-inform", "DER", "-nocrypt"],
444 stdin=devnull.fileno(),
445 stdout=devnull.fileno(),
446 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700447 p.communicate()
448 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700449 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700450 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700451 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700452 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
453 "-inform", "DER", "-passin", "pass:"],
454 stdin=devnull.fileno(),
455 stdout=devnull.fileno(),
456 stderr=subprocess.PIPE)
457 stdout, stderr = p.communicate()
458 if p.returncode == 0:
459 # Encrypted key with empty string as password.
460 key_passwords[k] = ''
461 elif stderr.startswith('Error decrypting key'):
462 # Definitely encrypted key.
463 # It would have said "Error reading key" if it didn't parse correctly.
464 need_passwords.append(k)
465 else:
466 # Potentially, a type of key that openssl doesn't understand.
467 # We'll let the routines in signapk.jar handle it.
468 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700469 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700470
T.R. Fullhart37e10522013-03-18 10:31:26 -0700471 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700472 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700473 return key_passwords
474
475
Doug Zongker951495f2009-08-14 12:44:19 -0700476def SignFile(input_name, output_name, key, password, align=None,
477 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700478 """Sign the input_name zip/jar/apk, producing output_name. Use the
479 given key and password (the latter may be None if the key does not
480 have a password.
481
482 If align is an integer > 1, zipalign is run to align stored files in
483 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700484
485 If whole_file is true, use the "-w" option to SignApk to embed a
486 signature that covers the whole file in the archive comment of the
487 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700488 """
Doug Zongker951495f2009-08-14 12:44:19 -0700489
Doug Zongkereef39442009-04-02 12:14:19 -0700490 if align == 0 or align == 1:
491 align = None
492
493 if align:
494 temp = tempfile.NamedTemporaryFile()
495 sign_name = temp.name
496 else:
497 sign_name = output_name
498
Baligh Uddin339ee492014-09-05 11:18:07 -0700499 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700500 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
501 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700502 if whole_file:
503 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700504 cmd.extend([key + OPTIONS.public_key_suffix,
505 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700506 input_name, sign_name])
507
508 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700509 if password is not None:
510 password += "\n"
511 p.communicate(password)
512 if p.returncode != 0:
513 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
514
515 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700516 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700517 p.communicate()
518 if p.returncode != 0:
519 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
520 temp.close()
521
522
Doug Zongker37974732010-09-16 17:44:38 -0700523def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700524 """Check the data string passed against the max size limit, if
525 any, for the given target. Raise exception if the data is too big.
526 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700527
Doug Zongker1684d9c2010-09-17 07:44:38 -0700528 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700529 mount_point = "/" + target
530
Ying Wangf8824af2014-06-03 14:07:27 -0700531 fs_type = None
532 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700533 if info_dict["fstab"]:
534 if mount_point == "/userdata": mount_point = "/data"
535 p = info_dict["fstab"][mount_point]
536 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800537 device = p.device
538 if "/" in device:
539 device = device[device.rfind("/")+1:]
540 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700541 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700542
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700543 if fs_type == "yaffs2":
544 # image size should be increased by 1/64th to account for the
545 # spare area (64 bytes per 2k page)
546 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800547 size = len(data)
548 pct = float(size) * 100.0 / limit
549 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
550 if pct >= 99.0:
551 raise ExternalError(msg)
552 elif pct >= 95.0:
553 print
554 print " WARNING: ", msg
555 print
556 elif OPTIONS.verbose:
557 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700558
559
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800560def ReadApkCerts(tf_zip):
561 """Given a target_files ZipFile, parse the META/apkcerts.txt file
562 and return a {package: cert} dict."""
563 certmap = {}
564 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
565 line = line.strip()
566 if not line: continue
567 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
568 r'private_key="(.*)"$', line)
569 if m:
570 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700571 public_key_suffix_len = len(OPTIONS.public_key_suffix)
572 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800573 if cert in SPECIAL_CERT_STRINGS and not privkey:
574 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700575 elif (cert.endswith(OPTIONS.public_key_suffix) and
576 privkey.endswith(OPTIONS.private_key_suffix) and
577 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
578 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800579 else:
580 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
581 return certmap
582
583
Doug Zongkereef39442009-04-02 12:14:19 -0700584COMMON_DOCSTRING = """
585 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700586 Prepend <dir>/bin to the list of places to search for binaries
587 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700588
Doug Zongker05d3dea2009-06-22 11:32:31 -0700589 -s (--device_specific) <file>
590 Path to the python module containing device-specific
591 releasetools code.
592
Doug Zongker8bec09e2009-11-30 15:37:14 -0800593 -x (--extra) <key=value>
594 Add a key/value pair to the 'extras' dict, which device-specific
595 extension code may look at.
596
Doug Zongkereef39442009-04-02 12:14:19 -0700597 -v (--verbose)
598 Show command lines being executed.
599
600 -h (--help)
601 Display this usage message and exit.
602"""
603
604def Usage(docstring):
605 print docstring.rstrip("\n")
606 print COMMON_DOCSTRING
607
608
609def ParseOptions(argv,
610 docstring,
611 extra_opts="", extra_long_opts=(),
612 extra_option_handler=None):
613 """Parse the options in argv and return any arguments that aren't
614 flags. docstring is the calling module's docstring, to be displayed
615 for errors and -h. extra_opts and extra_long_opts are for flags
616 defined by the caller, which are processed by passing them to
617 extra_option_handler."""
618
619 try:
620 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800621 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700622 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700623 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddine2048682014-11-20 09:52:05 -0800624 "private_key_suffix=", "boot_signer_path=", "device_specific=",
625 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700626 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700627 except getopt.GetoptError, err:
628 Usage(docstring)
629 print "**", str(err), "**"
630 sys.exit(2)
631
632 path_specified = False
633
634 for o, a in opts:
635 if o in ("-h", "--help"):
636 Usage(docstring)
637 sys.exit()
638 elif o in ("-v", "--verbose"):
639 OPTIONS.verbose = True
640 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700641 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700642 elif o in ("--signapk_path",):
643 OPTIONS.signapk_path = a
644 elif o in ("--extra_signapk_args",):
645 OPTIONS.extra_signapk_args = shlex.split(a)
646 elif o in ("--java_path",):
647 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700648 elif o in ("--java_args",):
649 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700650 elif o in ("--public_key_suffix",):
651 OPTIONS.public_key_suffix = a
652 elif o in ("--private_key_suffix",):
653 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800654 elif o in ("--boot_signer_path",):
655 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700656 elif o in ("-s", "--device_specific"):
657 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800658 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800659 key, value = a.split("=", 1)
660 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700661 else:
662 if extra_option_handler is None or not extra_option_handler(o, a):
663 assert False, "unknown option \"%s\"" % (o,)
664
Doug Zongker85448772014-09-09 14:59:20 -0700665 if OPTIONS.search_path:
666 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
667 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700668
669 return args
670
671
Doug Zongkerfc44a512014-08-26 13:10:25 -0700672def MakeTempFile(prefix=None, suffix=None):
673 """Make a temp file and add it to the list of things to be deleted
674 when Cleanup() is called. Return the filename."""
675 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
676 os.close(fd)
677 OPTIONS.tempfiles.append(fn)
678 return fn
679
680
Doug Zongkereef39442009-04-02 12:14:19 -0700681def Cleanup():
682 for i in OPTIONS.tempfiles:
683 if os.path.isdir(i):
684 shutil.rmtree(i)
685 else:
686 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700687
688
689class PasswordManager(object):
690 def __init__(self):
691 self.editor = os.getenv("EDITOR", None)
692 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
693
694 def GetPasswords(self, items):
695 """Get passwords corresponding to each string in 'items',
696 returning a dict. (The dict may have keys in addition to the
697 values in 'items'.)
698
699 Uses the passwords in $ANDROID_PW_FILE if available, letting the
700 user edit that file to add more needed passwords. If no editor is
701 available, or $ANDROID_PW_FILE isn't define, prompts the user
702 interactively in the ordinary way.
703 """
704
705 current = self.ReadFile()
706
707 first = True
708 while True:
709 missing = []
710 for i in items:
711 if i not in current or not current[i]:
712 missing.append(i)
713 # Are all the passwords already in the file?
714 if not missing: return current
715
716 for i in missing:
717 current[i] = ""
718
719 if not first:
720 print "key file %s still missing some passwords." % (self.pwfile,)
721 answer = raw_input("try to edit again? [y]> ").strip()
722 if answer and answer[0] not in 'yY':
723 raise RuntimeError("key passwords unavailable")
724 first = False
725
726 current = self.UpdateAndReadFile(current)
727
728 def PromptResult(self, current):
729 """Prompt the user to enter a value (password) for each key in
730 'current' whose value is fales. Returns a new dict with all the
731 values.
732 """
733 result = {}
734 for k, v in sorted(current.iteritems()):
735 if v:
736 result[k] = v
737 else:
738 while True:
739 result[k] = getpass.getpass("Enter password for %s key> "
740 % (k,)).strip()
741 if result[k]: break
742 return result
743
744 def UpdateAndReadFile(self, current):
745 if not self.editor or not self.pwfile:
746 return self.PromptResult(current)
747
748 f = open(self.pwfile, "w")
749 os.chmod(self.pwfile, 0600)
750 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
751 f.write("# (Additional spaces are harmless.)\n\n")
752
753 first_line = None
754 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
755 sorted.sort()
756 for i, (_, k, v) in enumerate(sorted):
757 f.write("[[[ %s ]]] %s\n" % (v, k))
758 if not v and first_line is None:
759 # position cursor on first line with no password.
760 first_line = i + 4
761 f.close()
762
763 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
764 _, _ = p.communicate()
765
766 return self.ReadFile()
767
768 def ReadFile(self):
769 result = {}
770 if self.pwfile is None: return result
771 try:
772 f = open(self.pwfile, "r")
773 for line in f:
774 line = line.strip()
775 if not line or line[0] == '#': continue
776 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
777 if not m:
778 print "failed to parse password file: ", line
779 else:
780 result[m.group(2)] = m.group(1)
781 f.close()
782 except IOError, e:
783 if e.errno != errno.ENOENT:
784 print "error reading password file: ", str(e)
785 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700786
787
Dan Albert8e0178d2015-01-27 15:53:15 -0800788def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
789 compress_type=None):
790 import datetime
791
792 # http://b/18015246
793 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
794 # for files larger than 2GiB. We can work around this by adjusting their
795 # limit. Note that `zipfile.writestr()` will not work for strings larger than
796 # 2GiB. The Python interpreter sometimes rejects strings that large (though
797 # it isn't clear to me exactly what circumstances cause this).
798 # `zipfile.write()` must be used directly to work around this.
799 #
800 # This mess can be avoided if we port to python3.
801 saved_zip64_limit = zipfile.ZIP64_LIMIT
802 zipfile.ZIP64_LIMIT = (1 << 32) - 1
803
804 if compress_type is None:
805 compress_type = zip_file.compression
806 if arcname is None:
807 arcname = filename
808
809 saved_stat = os.stat(filename)
810
811 try:
812 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
813 # file to be zipped and reset it when we're done.
814 os.chmod(filename, perms)
815
816 # Use a fixed timestamp so the output is repeatable.
817 epoch = datetime.datetime.fromtimestamp(0)
818 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
819 os.utime(filename, (timestamp, timestamp))
820
821 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
822 finally:
823 os.chmod(filename, saved_stat.st_mode)
824 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
825 zipfile.ZIP64_LIMIT = saved_zip64_limit
826
827
Geremy Condra36bd3652014-02-06 19:45:10 -0800828def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700829 # use a fixed timestamp so the output is repeatable.
830 zinfo = zipfile.ZipInfo(filename=filename,
831 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800832 if compression is None:
833 zinfo.compress_type = zip.compression
834 else:
835 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700836 zinfo.external_attr = perms << 16
837 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700838
839
840class DeviceSpecificParams(object):
841 module = None
842 def __init__(self, **kwargs):
843 """Keyword arguments to the constructor become attributes of this
844 object, which is passed to all functions in the device-specific
845 module."""
846 for k, v in kwargs.iteritems():
847 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800848 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700849
850 if self.module is None:
851 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700852 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700853 try:
854 if os.path.isdir(path):
855 info = imp.find_module("releasetools", [path])
856 else:
857 d, f = os.path.split(path)
858 b, x = os.path.splitext(f)
859 if x == ".py":
860 f = b
861 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800862 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700863 self.module = imp.load_module("device_specific", *info)
864 except ImportError:
865 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700866
867 def _DoCall(self, function_name, *args, **kwargs):
868 """Call the named function in the device-specific module, passing
869 the given args and kwargs. The first argument to the call will be
870 the DeviceSpecific object itself. If there is no module, or the
871 module does not define the function, return the value of the
872 'default' kwarg (which itself defaults to None)."""
873 if self.module is None or not hasattr(self.module, function_name):
874 return kwargs.get("default", None)
875 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
876
877 def FullOTA_Assertions(self):
878 """Called after emitting the block of assertions at the top of a
879 full OTA package. Implementations can add whatever additional
880 assertions they like."""
881 return self._DoCall("FullOTA_Assertions")
882
Doug Zongkere5ff5902012-01-17 10:55:37 -0800883 def FullOTA_InstallBegin(self):
884 """Called at the start of full OTA installation."""
885 return self._DoCall("FullOTA_InstallBegin")
886
Doug Zongker05d3dea2009-06-22 11:32:31 -0700887 def FullOTA_InstallEnd(self):
888 """Called at the end of full OTA installation; typically this is
889 used to install the image for the device's baseband processor."""
890 return self._DoCall("FullOTA_InstallEnd")
891
892 def IncrementalOTA_Assertions(self):
893 """Called after emitting the block of assertions at the top of an
894 incremental OTA package. Implementations can add whatever
895 additional assertions they like."""
896 return self._DoCall("IncrementalOTA_Assertions")
897
Doug Zongkere5ff5902012-01-17 10:55:37 -0800898 def IncrementalOTA_VerifyBegin(self):
899 """Called at the start of the verification phase of incremental
900 OTA installation; additional checks can be placed here to abort
901 the script before any changes are made."""
902 return self._DoCall("IncrementalOTA_VerifyBegin")
903
Doug Zongker05d3dea2009-06-22 11:32:31 -0700904 def IncrementalOTA_VerifyEnd(self):
905 """Called at the end of the verification phase of incremental OTA
906 installation; additional checks can be placed here to abort the
907 script before any changes are made."""
908 return self._DoCall("IncrementalOTA_VerifyEnd")
909
Doug Zongkere5ff5902012-01-17 10:55:37 -0800910 def IncrementalOTA_InstallBegin(self):
911 """Called at the start of incremental OTA installation (after
912 verification is complete)."""
913 return self._DoCall("IncrementalOTA_InstallBegin")
914
Doug Zongker05d3dea2009-06-22 11:32:31 -0700915 def IncrementalOTA_InstallEnd(self):
916 """Called at the end of incremental OTA installation; typically
917 this is used to install the image for the device's baseband
918 processor."""
919 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700920
921class File(object):
922 def __init__(self, name, data):
923 self.name = name
924 self.data = data
925 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800926 self.sha1 = sha1(data).hexdigest()
927
928 @classmethod
929 def FromLocalFile(cls, name, diskname):
930 f = open(diskname, "rb")
931 data = f.read()
932 f.close()
933 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700934
935 def WriteToTemp(self):
936 t = tempfile.NamedTemporaryFile()
937 t.write(self.data)
938 t.flush()
939 return t
940
Geremy Condra36bd3652014-02-06 19:45:10 -0800941 def AddToZip(self, z, compression=None):
942 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700943
944DIFF_PROGRAM_BY_EXT = {
945 ".gz" : "imgdiff",
946 ".zip" : ["imgdiff", "-z"],
947 ".jar" : ["imgdiff", "-z"],
948 ".apk" : ["imgdiff", "-z"],
949 ".img" : "imgdiff",
950 }
951
952class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700953 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700954 self.tf = tf
955 self.sf = sf
956 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700957 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700958
959 def ComputePatch(self):
960 """Compute the patch (as a string of data) needed to turn sf into
961 tf. Returns the same tuple as GetPatch()."""
962
963 tf = self.tf
964 sf = self.sf
965
Doug Zongker24cd2802012-08-14 16:36:15 -0700966 if self.diff_program:
967 diff_program = self.diff_program
968 else:
969 ext = os.path.splitext(tf.name)[1]
970 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700971
972 ttemp = tf.WriteToTemp()
973 stemp = sf.WriteToTemp()
974
975 ext = os.path.splitext(tf.name)[1]
976
977 try:
978 ptemp = tempfile.NamedTemporaryFile()
979 if isinstance(diff_program, list):
980 cmd = copy.copy(diff_program)
981 else:
982 cmd = [diff_program]
983 cmd.append(stemp.name)
984 cmd.append(ttemp.name)
985 cmd.append(ptemp.name)
986 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700987 err = []
988 def run():
989 _, e = p.communicate()
990 if e: err.append(e)
991 th = threading.Thread(target=run)
992 th.start()
993 th.join(timeout=300) # 5 mins
994 if th.is_alive():
995 print "WARNING: diff command timed out"
996 p.terminate()
997 th.join(5)
998 if th.is_alive():
999 p.kill()
1000 th.join()
1001
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001002 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001003 print "WARNING: failure running %s:\n%s\n" % (
1004 diff_program, "".join(err))
1005 self.patch = None
1006 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001007 diff = ptemp.read()
1008 finally:
1009 ptemp.close()
1010 stemp.close()
1011 ttemp.close()
1012
1013 self.patch = diff
1014 return self.tf, self.sf, self.patch
1015
1016
1017 def GetPatch(self):
1018 """Return a tuple (target_file, source_file, patch_data).
1019 patch_data may be None if ComputePatch hasn't been called, or if
1020 computing the patch failed."""
1021 return self.tf, self.sf, self.patch
1022
1023
1024def ComputeDifferences(diffs):
1025 """Call ComputePatch on all the Difference objects in 'diffs'."""
1026 print len(diffs), "diffs to compute"
1027
1028 # Do the largest files first, to try and reduce the long-pole effect.
1029 by_size = [(i.tf.size, i) for i in diffs]
1030 by_size.sort(reverse=True)
1031 by_size = [i[1] for i in by_size]
1032
1033 lock = threading.Lock()
1034 diff_iter = iter(by_size) # accessed under lock
1035
1036 def worker():
1037 try:
1038 lock.acquire()
1039 for d in diff_iter:
1040 lock.release()
1041 start = time.time()
1042 d.ComputePatch()
1043 dur = time.time() - start
1044 lock.acquire()
1045
1046 tf, sf, patch = d.GetPatch()
1047 if sf.name == tf.name:
1048 name = tf.name
1049 else:
1050 name = "%s (%s)" % (tf.name, sf.name)
1051 if patch is None:
1052 print "patching failed! %s" % (name,)
1053 else:
1054 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1055 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1056 lock.release()
1057 except Exception, e:
1058 print e
1059 raise
1060
1061 # start worker threads; wait for them all to finish.
1062 threads = [threading.Thread(target=worker)
1063 for i in range(OPTIONS.worker_threads)]
1064 for th in threads:
1065 th.start()
1066 while threads:
1067 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001068
1069
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001070class BlockDifference:
Tao Baodd2a5892015-03-12 12:32:37 -07001071 def __init__(self, partition, tgt, src=None, check_first_block=False, version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001072 self.tgt = tgt
1073 self.src = src
1074 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001075 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001076
Tao Baodd2a5892015-03-12 12:32:37 -07001077 if version is None:
1078 version = 1
1079 if OPTIONS.info_dict:
1080 version = max(
1081 int(i) for i in
1082 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1083 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001084
1085 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001086 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001087 tmpdir = tempfile.mkdtemp()
1088 OPTIONS.tempfiles.append(tmpdir)
1089 self.path = os.path.join(tmpdir, partition)
1090 b.Compute(self.path)
1091
1092 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1093
1094 def WriteScript(self, script, output_zip, progress=None):
1095 if not self.src:
1096 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001097 script.Print("Patching %s image unconditionally..." % (self.partition,))
1098 else:
1099 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001100
Jesse Zhao75bcea02015-01-06 10:59:53 -08001101 if progress: script.ShowProgress(progress, 0)
1102 self._WriteUpdate(script, output_zip)
1103
1104 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001105 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001106 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001107 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001108 else:
Michael Runge910b0052015-02-11 19:28:08 -08001109 if self.version >= 3:
1110 script.AppendExtra(('if block_image_verify("%s", '
1111 'package_extract_file("%s.transfer.list"), '
1112 '"%s.new.dat", "%s.patch.dat") then') %
1113 (self.device, partition, partition, partition))
1114 else:
1115 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1116 (self.device, self.src.care_map.to_string_raw(),
1117 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001118 script.Print('Verified %s image...' % (partition,))
Sami Tolvanendd67a292014-12-09 16:40:34 +00001119 script.AppendExtra('else');
1120
Tao Baodd2a5892015-03-12 12:32:37 -07001121 # When generating incrementals for the system and vendor partitions,
1122 # explicitly check the first block (which contains the superblock) of
1123 # the partition to see if it's what we expect. If this check fails,
1124 # give an explicit log message about the partition having been
1125 # remounted R/W (the most likely explanation) and the need to flash to
1126 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001127 if self.check_first_block:
1128 self._CheckFirstBlock(script)
1129
Tao Baodd2a5892015-03-12 12:32:37 -07001130 # Abort the OTA update. Note that the incremental OTA cannot be applied
1131 # even if it may match the checksum of the target partition.
1132 # a) If version < 3, operations like move and erase will make changes
1133 # unconditionally and damage the partition.
1134 # b) If version >= 3, it won't even reach here.
1135 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1136 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001137
1138 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001139 ZipWrite(output_zip,
1140 '{}.transfer.list'.format(self.path),
1141 '{}.transfer.list'.format(self.partition))
1142 ZipWrite(output_zip,
1143 '{}.new.dat'.format(self.path),
1144 '{}.new.dat'.format(self.partition))
1145 ZipWrite(output_zip,
1146 '{}.patch.dat'.format(self.path),
1147 '{}.patch.dat'.format(self.partition),
1148 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001149
Dan Albert8e0178d2015-01-27 15:53:15 -08001150 call = ('block_image_update("{device}", '
1151 'package_extract_file("{partition}.transfer.list"), '
1152 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1153 device=self.device, partition=self.partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001154 script.AppendExtra(script._WordWrap(call))
1155
Sami Tolvanendd67a292014-12-09 16:40:34 +00001156 def _HashBlocks(self, source, ranges):
1157 data = source.ReadRangeSet(ranges)
1158 ctx = sha1()
1159
1160 for p in data:
1161 ctx.update(p)
1162
1163 return ctx.hexdigest()
1164
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001165 def _CheckFirstBlock(self, script):
1166 r = RangeSet((0, 1))
Sami Tolvanendd67a292014-12-09 16:40:34 +00001167 srchash = self._HashBlocks(self.src, r);
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001168
1169 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1170 'abort("%s has been remounted R/W; '
1171 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001172 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001173 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001174
1175DataImage = blockimgdiff.DataImage
1176
1177
Doug Zongker96a57e72010-09-26 14:57:41 -07001178# map recovery.fstab's fs_types to mount/format "partition types"
1179PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001180 "ext4": "EMMC", "emmc": "EMMC",
1181 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001182
1183def GetTypeAndDevice(mount_point, info):
1184 fstab = info["fstab"]
1185 if fstab:
1186 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1187 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001188 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001189
1190
1191def ParseCertificate(data):
1192 """Parse a PEM-format certificate."""
1193 cert = []
1194 save = False
1195 for line in data.split("\n"):
1196 if "--END CERTIFICATE--" in line:
1197 break
1198 if save:
1199 cert.append(line)
1200 if "--BEGIN CERTIFICATE--" in line:
1201 save = True
1202 cert = "".join(cert).decode('base64')
1203 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001204
Doug Zongker412c02f2014-02-13 10:58:24 -08001205def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1206 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001207 """Generate a binary patch that creates the recovery image starting
1208 with the boot image. (Most of the space in these images is just the
1209 kernel, which is identical for the two, so the resulting patch
1210 should be efficient.) Add it to the output zip, along with a shell
1211 script that is run from init.rc on first boot to actually do the
1212 patching and install the new recovery image.
1213
1214 recovery_img and boot_img should be File objects for the
1215 corresponding images. info should be the dictionary returned by
1216 common.LoadInfoDict() on the input target_files.
1217 """
1218
Doug Zongker412c02f2014-02-13 10:58:24 -08001219 if info_dict is None:
1220 info_dict = OPTIONS.info_dict
1221
Doug Zongkerc9253822014-02-04 12:17:58 -08001222 diff_program = ["imgdiff"]
1223 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1224 if os.path.exists(path):
1225 diff_program.append("-b")
1226 diff_program.append(path)
1227 bonus_args = "-b /system/etc/recovery-resource.dat"
1228 else:
1229 bonus_args = ""
1230
1231 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1232 _, _, patch = d.ComputePatch()
1233 output_sink("recovery-from-boot.p", patch)
1234
Ying Wanga961a092014-07-29 11:42:37 -07001235 td_pair = GetTypeAndDevice("/boot", info_dict)
1236 if not td_pair:
1237 return
1238 boot_type, boot_device = td_pair
1239 td_pair = GetTypeAndDevice("/recovery", info_dict)
1240 if not td_pair:
1241 return
1242 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001243
1244 sh = """#!/system/bin/sh
1245if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1246 applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1247else
1248 log -t recovery "Recovery image already installed"
1249fi
1250""" % { 'boot_size': boot_img.size,
1251 'boot_sha1': boot_img.sha1,
1252 'recovery_size': recovery_img.size,
1253 'recovery_sha1': recovery_img.sha1,
1254 'boot_type': boot_type,
1255 'boot_device': boot_device,
1256 'recovery_type': recovery_type,
1257 'recovery_device': recovery_device,
1258 'bonus_args': bonus_args,
1259 }
1260
1261 # The install script location moved from /system/etc to /system/bin
1262 # in the L release. Parse the init.rc file to find out where the
1263 # target-files expects it to be, and put it there.
1264 sh_location = "etc/install-recovery.sh"
1265 try:
1266 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1267 for line in f:
1268 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1269 if m:
1270 sh_location = m.group(1)
1271 print "putting script in", sh_location
1272 break
1273 except (OSError, IOError), e:
1274 print "failed to read init.rc: %s" % (e,)
1275
1276 output_sink(sh_location, sh)