blob: 92d912bd6a3289434c144f36c9d7c02f78637178 [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
33
Doug Zongker55d93282011-01-25 17:03:34 -080034try:
davidcad0bb92011-03-15 14:21:38 +000035 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036except ImportError:
davidcad0bb92011-03-15 14:21:38 +000037 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080038
Doug Zongkereef39442009-04-02 12:14:19 -070039# missing in Python 2.4 and before
40if not hasattr(os, "SEEK_SET"):
41 os.SEEK_SET = 0
42
43class Options(object): pass
44OPTIONS = Options()
Doug Zongker85448772014-09-09 14:59:20 -070045
46DEFAULT_SEARCH_PATH_BY_PLATFORM = {
47 "linux2": "out/host/linux-x86",
48 "darwin": "out/host/darwin-x86",
49 }
50OPTIONS.search_path = DEFAULT_SEARCH_PATH_BY_PLATFORM.get(sys.platform, None)
51
T.R. Fullhart37e10522013-03-18 10:31:26 -070052OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
53OPTIONS.extra_signapk_args = []
54OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070055OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070056OPTIONS.public_key_suffix = ".x509.pem"
57OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070058OPTIONS.verbose = False
59OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070060OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080061OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070062OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Doug Zongkereef39442009-04-02 12:14:19 -070069class ExternalError(RuntimeError): pass
70
71
72def Run(args, **kwargs):
73 """Create and return a subprocess.Popen object, printing the command
74 line on the terminal if -v was specified."""
75 if OPTIONS.verbose:
76 print " running: ", " ".join(args)
77 return subprocess.Popen(args, **kwargs)
78
79
Ying Wang7e6d4e42010-12-13 16:25:36 -080080def CloseInheritedPipes():
81 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
82 before doing other work."""
83 if platform.system() != "Darwin":
84 return
85 for d in range(3, 1025):
86 try:
87 stat = os.fstat(d)
88 if stat is not None:
89 pipebit = stat[0] & 0x1000
90 if pipebit != 0:
91 os.close(d)
92 except OSError:
93 pass
94
95
Doug Zongkerc9253822014-02-04 12:17:58 -080096def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070097 """Read and parse the META/misc_info.txt key/value pairs from the
98 input target files and return a dict."""
99
Doug Zongkerc9253822014-02-04 12:17:58 -0800100 def read_helper(fn):
101 if isinstance(input, zipfile.ZipFile):
102 return input.read(fn)
103 else:
104 path = os.path.join(input, *fn.split("/"))
105 try:
106 with open(path) as f:
107 return f.read()
108 except IOError, e:
109 if e.errno == errno.ENOENT:
110 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700111 d = {}
112 try:
Michael Runge6e836112014-04-15 17:40:21 -0700113 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700114 except KeyError:
115 # ok if misc_info.txt doesn't exist
116 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700117
Doug Zongker37974732010-09-16 17:44:38 -0700118 # backwards compatibility: These values used to be in their own
119 # files. Look for them, in case we're processing an old
120 # target_files zip.
121
122 if "mkyaffs2_extra_flags" not in d:
123 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800124 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700125 except KeyError:
126 # ok if flags don't exist
127 pass
128
129 if "recovery_api_version" not in d:
130 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800131 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700132 except KeyError:
133 raise ValueError("can't find recovery API version in input target-files")
134
135 if "tool_extensions" not in d:
136 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800137 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 # ok if extensions don't exist
140 pass
141
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800142 if "fstab_version" not in d:
143 d["fstab_version"] = "1"
144
Doug Zongker37974732010-09-16 17:44:38 -0700145 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800146 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700147 for line in data.split("\n"):
148 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700149 name, value = line.split(" ", 1)
150 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700151 if name == "blocksize":
152 d[name] = value
153 else:
154 d[name + "_size"] = value
155 except KeyError:
156 pass
157
158 def makeint(key):
159 if key in d:
160 d[key] = int(d[key], 0)
161
162 makeint("recovery_api_version")
163 makeint("blocksize")
164 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700165 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700166 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700167 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700168 makeint("recovery_size")
169 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800170 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700171
Doug Zongkerc9253822014-02-04 12:17:58 -0800172 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
173 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700174 return d
175
Doug Zongkerc9253822014-02-04 12:17:58 -0800176def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700177 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800178 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 except KeyError:
180 print "Warning: could not find SYSTEM/build.prop in %s" % zip
181 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700182 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700183
Michael Runge6e836112014-04-15 17:40:21 -0700184def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700186 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 line = line.strip()
188 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700189 if "=" in line:
190 name, value = line.split("=", 1)
191 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700192 return d
193
Doug Zongkerc9253822014-02-04 12:17:58 -0800194def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700195 class Partition(object):
196 pass
197
198 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800199 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700200 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800201 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700202 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700203
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800204 if fstab_version == 1:
205 d = {}
206 for line in data.split("\n"):
207 line = line.strip()
208 if not line or line.startswith("#"): continue
209 pieces = line.split()
210 if not (3 <= len(pieces) <= 4):
211 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800213 p = Partition()
214 p.mount_point = pieces[0]
215 p.fs_type = pieces[1]
216 p.device = pieces[2]
217 p.length = 0
218 options = None
219 if len(pieces) >= 4:
220 if pieces[3].startswith("/"):
221 p.device2 = pieces[3]
222 if len(pieces) >= 5:
223 options = pieces[4]
224 else:
225 p.device2 = None
226 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800227 else:
228 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700229
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 if options:
231 options = options.split(",")
232 for i in options:
233 if i.startswith("length="):
234 p.length = int(i[7:])
235 else:
236 print "%s: unknown option \"%s\"" % (p.mount_point, i)
237
238 d[p.mount_point] = p
239
240 elif fstab_version == 2:
241 d = {}
242 for line in data.split("\n"):
243 line = line.strip()
244 if not line or line.startswith("#"): continue
245 pieces = line.split()
246 if len(pieces) != 5:
247 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
248
249 # Ignore entries that are managed by vold
250 options = pieces[4]
251 if "voldmanaged=" in options: continue
252
253 # It's a good line, parse it
254 p = Partition()
255 p.device = pieces[0]
256 p.mount_point = pieces[1]
257 p.fs_type = pieces[2]
258 p.device2 = None
259 p.length = 0
260
Doug Zongker086cbb02011-02-17 15:54:20 -0800261 options = options.split(",")
262 for i in options:
263 if i.startswith("length="):
264 p.length = int(i[7:])
265 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800266 # Ignore all unknown options in the unified fstab
267 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800268
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269 d[p.mount_point] = p
270
271 else:
272 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
273
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700274 return d
275
276
Doug Zongker37974732010-09-16 17:44:38 -0700277def DumpInfoDict(d):
278 for k, v in sorted(d.items()):
279 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700280
Doug Zongkerd5131602012-08-02 14:46:42 -0700281def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700282 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700283 'sourcedir'), and turn them into a boot image. Return the image
284 data, or None if sourcedir does not appear to contains files for
285 building the requested image."""
286
287 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
288 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
289 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700290
Doug Zongkerd5131602012-08-02 14:46:42 -0700291 if info_dict is None:
292 info_dict = OPTIONS.info_dict
293
Doug Zongkereef39442009-04-02 12:14:19 -0700294 ramdisk_img = tempfile.NamedTemporaryFile()
295 img = tempfile.NamedTemporaryFile()
296
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700297 if os.access(fs_config_file, os.F_OK):
298 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
299 else:
300 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
301 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700302 p2 = Run(["minigzip"],
303 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700304
305 p2.wait()
306 p1.wait()
307 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700308 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700309
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800310 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
311 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
312
313 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700314
Benoit Fradina45a8682014-07-14 21:00:43 +0200315 fn = os.path.join(sourcedir, "second")
316 if os.access(fn, os.F_OK):
317 cmd.append("--second")
318 cmd.append(fn)
319
Doug Zongker171f1cd2009-06-15 22:36:37 -0700320 fn = os.path.join(sourcedir, "cmdline")
321 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700322 cmd.append("--cmdline")
323 cmd.append(open(fn).read().rstrip("\n"))
324
325 fn = os.path.join(sourcedir, "base")
326 if os.access(fn, os.F_OK):
327 cmd.append("--base")
328 cmd.append(open(fn).read().rstrip("\n"))
329
Ying Wang4de6b5b2010-08-25 14:29:34 -0700330 fn = os.path.join(sourcedir, "pagesize")
331 if os.access(fn, os.F_OK):
332 cmd.append("--pagesize")
333 cmd.append(open(fn).read().rstrip("\n"))
334
Doug Zongkerd5131602012-08-02 14:46:42 -0700335 args = info_dict.get("mkbootimg_args", None)
336 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700337 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700338
Doug Zongker38a649f2009-06-17 09:07:09 -0700339 cmd.extend(["--ramdisk", ramdisk_img.name,
340 "--output", img.name])
341
342 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700343 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700344 assert p.returncode == 0, "mkbootimg of %s image failed" % (
345 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700346
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700347 if info_dict.get("verity_key", None):
348 path = "/" + os.path.basename(sourcedir).lower()
349 cmd = ["boot_signer", path, img.name, info_dict["verity_key"], img.name]
350 p = Run(cmd, stdout=subprocess.PIPE)
351 p.communicate()
352 assert p.returncode == 0, "boot_signer of %s image failed" % path
353
Doug Zongkereef39442009-04-02 12:14:19 -0700354 img.seek(os.SEEK_SET, 0)
355 data = img.read()
356
357 ramdisk_img.close()
358 img.close()
359
360 return data
361
362
Doug Zongkerd5131602012-08-02 14:46:42 -0700363def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
364 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800365 """Return a File object (with name 'name') with the desired bootable
366 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700367 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
368 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800369 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700370
Doug Zongker55d93282011-01-25 17:03:34 -0800371 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
372 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700373 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800374 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700375
376 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
377 if os.path.exists(prebuilt_path):
378 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
379 return File.FromLocalFile(name, prebuilt_path)
380
381 print "building image from target_files %s..." % (tree_subdir,)
382 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
383 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
384 os.path.join(unpack_dir, fs_config),
385 info_dict)
386 if data:
387 return File(name, data)
388 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800389
Doug Zongkereef39442009-04-02 12:14:19 -0700390
Doug Zongker75f17362009-12-08 13:46:44 -0800391def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800392 """Unzip the given archive into a temporary directory and return the name.
393
394 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
395 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
396
397 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
398 main file), open for reading.
399 """
Doug Zongkereef39442009-04-02 12:14:19 -0700400
401 tmp = tempfile.mkdtemp(prefix="targetfiles-")
402 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800403
404 def unzip_to_dir(filename, dirname):
405 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
406 if pattern is not None:
407 cmd.append(pattern)
408 p = Run(cmd, stdout=subprocess.PIPE)
409 p.communicate()
410 if p.returncode != 0:
411 raise ExternalError("failed to unzip input target-files \"%s\"" %
412 (filename,))
413
414 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
415 if m:
416 unzip_to_dir(m.group(1), tmp)
417 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
418 filename = m.group(1)
419 else:
420 unzip_to_dir(filename, tmp)
421
422 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700423
424
425def GetKeyPasswords(keylist):
426 """Given a list of keys, prompt the user to enter passwords for
427 those which require them. Return a {key: password} dict. password
428 will be None if the key has no password."""
429
Doug Zongker8ce7c252009-05-22 13:34:54 -0700430 no_passwords = []
431 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700432 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700433 devnull = open("/dev/null", "w+b")
434 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800435 # We don't need a password for things that aren't really keys.
436 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700437 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700438 continue
439
T.R. Fullhart37e10522013-03-18 10:31:26 -0700440 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700441 "-inform", "DER", "-nocrypt"],
442 stdin=devnull.fileno(),
443 stdout=devnull.fileno(),
444 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700445 p.communicate()
446 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700447 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700448 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700449 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700450 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
451 "-inform", "DER", "-passin", "pass:"],
452 stdin=devnull.fileno(),
453 stdout=devnull.fileno(),
454 stderr=subprocess.PIPE)
455 stdout, stderr = p.communicate()
456 if p.returncode == 0:
457 # Encrypted key with empty string as password.
458 key_passwords[k] = ''
459 elif stderr.startswith('Error decrypting key'):
460 # Definitely encrypted key.
461 # It would have said "Error reading key" if it didn't parse correctly.
462 need_passwords.append(k)
463 else:
464 # Potentially, a type of key that openssl doesn't understand.
465 # We'll let the routines in signapk.jar handle it.
466 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700467 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700468
T.R. Fullhart37e10522013-03-18 10:31:26 -0700469 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700470 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700471 return key_passwords
472
473
Doug Zongker951495f2009-08-14 12:44:19 -0700474def SignFile(input_name, output_name, key, password, align=None,
475 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700476 """Sign the input_name zip/jar/apk, producing output_name. Use the
477 given key and password (the latter may be None if the key does not
478 have a password.
479
480 If align is an integer > 1, zipalign is run to align stored files in
481 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700482
483 If whole_file is true, use the "-w" option to SignApk to embed a
484 signature that covers the whole file in the archive comment of the
485 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700486 """
Doug Zongker951495f2009-08-14 12:44:19 -0700487
Doug Zongkereef39442009-04-02 12:14:19 -0700488 if align == 0 or align == 1:
489 align = None
490
491 if align:
492 temp = tempfile.NamedTemporaryFile()
493 sign_name = temp.name
494 else:
495 sign_name = output_name
496
Baligh Uddin339ee492014-09-05 11:18:07 -0700497 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700498 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
499 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700500 if whole_file:
501 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700502 cmd.extend([key + OPTIONS.public_key_suffix,
503 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700504 input_name, sign_name])
505
506 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700507 if password is not None:
508 password += "\n"
509 p.communicate(password)
510 if p.returncode != 0:
511 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
512
513 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700514 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700515 p.communicate()
516 if p.returncode != 0:
517 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
518 temp.close()
519
520
Doug Zongker37974732010-09-16 17:44:38 -0700521def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700522 """Check the data string passed against the max size limit, if
523 any, for the given target. Raise exception if the data is too big.
524 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700525
Doug Zongker1684d9c2010-09-17 07:44:38 -0700526 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700527 mount_point = "/" + target
528
Ying Wangf8824af2014-06-03 14:07:27 -0700529 fs_type = None
530 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700531 if info_dict["fstab"]:
532 if mount_point == "/userdata": mount_point = "/data"
533 p = info_dict["fstab"][mount_point]
534 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800535 device = p.device
536 if "/" in device:
537 device = device[device.rfind("/")+1:]
538 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700539 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700540
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700541 if fs_type == "yaffs2":
542 # image size should be increased by 1/64th to account for the
543 # spare area (64 bytes per 2k page)
544 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800545 size = len(data)
546 pct = float(size) * 100.0 / limit
547 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
548 if pct >= 99.0:
549 raise ExternalError(msg)
550 elif pct >= 95.0:
551 print
552 print " WARNING: ", msg
553 print
554 elif OPTIONS.verbose:
555 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700556
557
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800558def ReadApkCerts(tf_zip):
559 """Given a target_files ZipFile, parse the META/apkcerts.txt file
560 and return a {package: cert} dict."""
561 certmap = {}
562 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
563 line = line.strip()
564 if not line: continue
565 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
566 r'private_key="(.*)"$', line)
567 if m:
568 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700569 public_key_suffix_len = len(OPTIONS.public_key_suffix)
570 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800571 if cert in SPECIAL_CERT_STRINGS and not privkey:
572 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700573 elif (cert.endswith(OPTIONS.public_key_suffix) and
574 privkey.endswith(OPTIONS.private_key_suffix) and
575 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
576 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800577 else:
578 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
579 return certmap
580
581
Doug Zongkereef39442009-04-02 12:14:19 -0700582COMMON_DOCSTRING = """
583 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700584 Prepend <dir>/bin to the list of places to search for binaries
585 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700586
Doug Zongker05d3dea2009-06-22 11:32:31 -0700587 -s (--device_specific) <file>
588 Path to the python module containing device-specific
589 releasetools code.
590
Doug Zongker8bec09e2009-11-30 15:37:14 -0800591 -x (--extra) <key=value>
592 Add a key/value pair to the 'extras' dict, which device-specific
593 extension code may look at.
594
Doug Zongkereef39442009-04-02 12:14:19 -0700595 -v (--verbose)
596 Show command lines being executed.
597
598 -h (--help)
599 Display this usage message and exit.
600"""
601
602def Usage(docstring):
603 print docstring.rstrip("\n")
604 print COMMON_DOCSTRING
605
606
607def ParseOptions(argv,
608 docstring,
609 extra_opts="", extra_long_opts=(),
610 extra_option_handler=None):
611 """Parse the options in argv and return any arguments that aren't
612 flags. docstring is the calling module's docstring, to be displayed
613 for errors and -h. extra_opts and extra_long_opts are for flags
614 defined by the caller, which are processed by passing them to
615 extra_option_handler."""
616
617 try:
618 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800619 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700620 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700621 "java_path=", "java_args=", "public_key_suffix=",
622 "private_key_suffix=", "device_specific=", "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700623 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700624 except getopt.GetoptError, err:
625 Usage(docstring)
626 print "**", str(err), "**"
627 sys.exit(2)
628
629 path_specified = False
630
631 for o, a in opts:
632 if o in ("-h", "--help"):
633 Usage(docstring)
634 sys.exit()
635 elif o in ("-v", "--verbose"):
636 OPTIONS.verbose = True
637 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700638 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700639 elif o in ("--signapk_path",):
640 OPTIONS.signapk_path = a
641 elif o in ("--extra_signapk_args",):
642 OPTIONS.extra_signapk_args = shlex.split(a)
643 elif o in ("--java_path",):
644 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700645 elif o in ("--java_args",):
646 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700647 elif o in ("--public_key_suffix",):
648 OPTIONS.public_key_suffix = a
649 elif o in ("--private_key_suffix",):
650 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700651 elif o in ("-s", "--device_specific"):
652 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800653 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800654 key, value = a.split("=", 1)
655 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700656 else:
657 if extra_option_handler is None or not extra_option_handler(o, a):
658 assert False, "unknown option \"%s\"" % (o,)
659
Doug Zongker85448772014-09-09 14:59:20 -0700660 if OPTIONS.search_path:
661 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
662 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700663
664 return args
665
666
Doug Zongkerfc44a512014-08-26 13:10:25 -0700667def MakeTempFile(prefix=None, suffix=None):
668 """Make a temp file and add it to the list of things to be deleted
669 when Cleanup() is called. Return the filename."""
670 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
671 os.close(fd)
672 OPTIONS.tempfiles.append(fn)
673 return fn
674
675
Doug Zongkereef39442009-04-02 12:14:19 -0700676def Cleanup():
677 for i in OPTIONS.tempfiles:
678 if os.path.isdir(i):
679 shutil.rmtree(i)
680 else:
681 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700682
683
684class PasswordManager(object):
685 def __init__(self):
686 self.editor = os.getenv("EDITOR", None)
687 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
688
689 def GetPasswords(self, items):
690 """Get passwords corresponding to each string in 'items',
691 returning a dict. (The dict may have keys in addition to the
692 values in 'items'.)
693
694 Uses the passwords in $ANDROID_PW_FILE if available, letting the
695 user edit that file to add more needed passwords. If no editor is
696 available, or $ANDROID_PW_FILE isn't define, prompts the user
697 interactively in the ordinary way.
698 """
699
700 current = self.ReadFile()
701
702 first = True
703 while True:
704 missing = []
705 for i in items:
706 if i not in current or not current[i]:
707 missing.append(i)
708 # Are all the passwords already in the file?
709 if not missing: return current
710
711 for i in missing:
712 current[i] = ""
713
714 if not first:
715 print "key file %s still missing some passwords." % (self.pwfile,)
716 answer = raw_input("try to edit again? [y]> ").strip()
717 if answer and answer[0] not in 'yY':
718 raise RuntimeError("key passwords unavailable")
719 first = False
720
721 current = self.UpdateAndReadFile(current)
722
723 def PromptResult(self, current):
724 """Prompt the user to enter a value (password) for each key in
725 'current' whose value is fales. Returns a new dict with all the
726 values.
727 """
728 result = {}
729 for k, v in sorted(current.iteritems()):
730 if v:
731 result[k] = v
732 else:
733 while True:
734 result[k] = getpass.getpass("Enter password for %s key> "
735 % (k,)).strip()
736 if result[k]: break
737 return result
738
739 def UpdateAndReadFile(self, current):
740 if not self.editor or not self.pwfile:
741 return self.PromptResult(current)
742
743 f = open(self.pwfile, "w")
744 os.chmod(self.pwfile, 0600)
745 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
746 f.write("# (Additional spaces are harmless.)\n\n")
747
748 first_line = None
749 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
750 sorted.sort()
751 for i, (_, k, v) in enumerate(sorted):
752 f.write("[[[ %s ]]] %s\n" % (v, k))
753 if not v and first_line is None:
754 # position cursor on first line with no password.
755 first_line = i + 4
756 f.close()
757
758 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
759 _, _ = p.communicate()
760
761 return self.ReadFile()
762
763 def ReadFile(self):
764 result = {}
765 if self.pwfile is None: return result
766 try:
767 f = open(self.pwfile, "r")
768 for line in f:
769 line = line.strip()
770 if not line or line[0] == '#': continue
771 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
772 if not m:
773 print "failed to parse password file: ", line
774 else:
775 result[m.group(2)] = m.group(1)
776 f.close()
777 except IOError, e:
778 if e.errno != errno.ENOENT:
779 print "error reading password file: ", str(e)
780 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700781
782
Geremy Condra36bd3652014-02-06 19:45:10 -0800783def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700784 # use a fixed timestamp so the output is repeatable.
785 zinfo = zipfile.ZipInfo(filename=filename,
786 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800787 if compression is None:
788 zinfo.compress_type = zip.compression
789 else:
790 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700791 zinfo.external_attr = perms << 16
792 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700793
794
795class DeviceSpecificParams(object):
796 module = None
797 def __init__(self, **kwargs):
798 """Keyword arguments to the constructor become attributes of this
799 object, which is passed to all functions in the device-specific
800 module."""
801 for k, v in kwargs.iteritems():
802 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800803 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700804
805 if self.module is None:
806 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700807 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700808 try:
809 if os.path.isdir(path):
810 info = imp.find_module("releasetools", [path])
811 else:
812 d, f = os.path.split(path)
813 b, x = os.path.splitext(f)
814 if x == ".py":
815 f = b
816 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800817 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700818 self.module = imp.load_module("device_specific", *info)
819 except ImportError:
820 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700821
822 def _DoCall(self, function_name, *args, **kwargs):
823 """Call the named function in the device-specific module, passing
824 the given args and kwargs. The first argument to the call will be
825 the DeviceSpecific object itself. If there is no module, or the
826 module does not define the function, return the value of the
827 'default' kwarg (which itself defaults to None)."""
828 if self.module is None or not hasattr(self.module, function_name):
829 return kwargs.get("default", None)
830 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
831
832 def FullOTA_Assertions(self):
833 """Called after emitting the block of assertions at the top of a
834 full OTA package. Implementations can add whatever additional
835 assertions they like."""
836 return self._DoCall("FullOTA_Assertions")
837
Doug Zongkere5ff5902012-01-17 10:55:37 -0800838 def FullOTA_InstallBegin(self):
839 """Called at the start of full OTA installation."""
840 return self._DoCall("FullOTA_InstallBegin")
841
Doug Zongker05d3dea2009-06-22 11:32:31 -0700842 def FullOTA_InstallEnd(self):
843 """Called at the end of full OTA installation; typically this is
844 used to install the image for the device's baseband processor."""
845 return self._DoCall("FullOTA_InstallEnd")
846
847 def IncrementalOTA_Assertions(self):
848 """Called after emitting the block of assertions at the top of an
849 incremental OTA package. Implementations can add whatever
850 additional assertions they like."""
851 return self._DoCall("IncrementalOTA_Assertions")
852
Doug Zongkere5ff5902012-01-17 10:55:37 -0800853 def IncrementalOTA_VerifyBegin(self):
854 """Called at the start of the verification phase of incremental
855 OTA installation; additional checks can be placed here to abort
856 the script before any changes are made."""
857 return self._DoCall("IncrementalOTA_VerifyBegin")
858
Doug Zongker05d3dea2009-06-22 11:32:31 -0700859 def IncrementalOTA_VerifyEnd(self):
860 """Called at the end of the verification phase of incremental OTA
861 installation; additional checks can be placed here to abort the
862 script before any changes are made."""
863 return self._DoCall("IncrementalOTA_VerifyEnd")
864
Doug Zongkere5ff5902012-01-17 10:55:37 -0800865 def IncrementalOTA_InstallBegin(self):
866 """Called at the start of incremental OTA installation (after
867 verification is complete)."""
868 return self._DoCall("IncrementalOTA_InstallBegin")
869
Doug Zongker05d3dea2009-06-22 11:32:31 -0700870 def IncrementalOTA_InstallEnd(self):
871 """Called at the end of incremental OTA installation; typically
872 this is used to install the image for the device's baseband
873 processor."""
874 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700875
876class File(object):
877 def __init__(self, name, data):
878 self.name = name
879 self.data = data
880 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800881 self.sha1 = sha1(data).hexdigest()
882
883 @classmethod
884 def FromLocalFile(cls, name, diskname):
885 f = open(diskname, "rb")
886 data = f.read()
887 f.close()
888 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700889
890 def WriteToTemp(self):
891 t = tempfile.NamedTemporaryFile()
892 t.write(self.data)
893 t.flush()
894 return t
895
Geremy Condra36bd3652014-02-06 19:45:10 -0800896 def AddToZip(self, z, compression=None):
897 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700898
899DIFF_PROGRAM_BY_EXT = {
900 ".gz" : "imgdiff",
901 ".zip" : ["imgdiff", "-z"],
902 ".jar" : ["imgdiff", "-z"],
903 ".apk" : ["imgdiff", "-z"],
904 ".img" : "imgdiff",
905 }
906
907class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700908 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700909 self.tf = tf
910 self.sf = sf
911 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700912 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700913
914 def ComputePatch(self):
915 """Compute the patch (as a string of data) needed to turn sf into
916 tf. Returns the same tuple as GetPatch()."""
917
918 tf = self.tf
919 sf = self.sf
920
Doug Zongker24cd2802012-08-14 16:36:15 -0700921 if self.diff_program:
922 diff_program = self.diff_program
923 else:
924 ext = os.path.splitext(tf.name)[1]
925 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700926
927 ttemp = tf.WriteToTemp()
928 stemp = sf.WriteToTemp()
929
930 ext = os.path.splitext(tf.name)[1]
931
932 try:
933 ptemp = tempfile.NamedTemporaryFile()
934 if isinstance(diff_program, list):
935 cmd = copy.copy(diff_program)
936 else:
937 cmd = [diff_program]
938 cmd.append(stemp.name)
939 cmd.append(ttemp.name)
940 cmd.append(ptemp.name)
941 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700942 err = []
943 def run():
944 _, e = p.communicate()
945 if e: err.append(e)
946 th = threading.Thread(target=run)
947 th.start()
948 th.join(timeout=300) # 5 mins
949 if th.is_alive():
950 print "WARNING: diff command timed out"
951 p.terminate()
952 th.join(5)
953 if th.is_alive():
954 p.kill()
955 th.join()
956
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700957 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700958 print "WARNING: failure running %s:\n%s\n" % (
959 diff_program, "".join(err))
960 self.patch = None
961 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700962 diff = ptemp.read()
963 finally:
964 ptemp.close()
965 stemp.close()
966 ttemp.close()
967
968 self.patch = diff
969 return self.tf, self.sf, self.patch
970
971
972 def GetPatch(self):
973 """Return a tuple (target_file, source_file, patch_data).
974 patch_data may be None if ComputePatch hasn't been called, or if
975 computing the patch failed."""
976 return self.tf, self.sf, self.patch
977
978
979def ComputeDifferences(diffs):
980 """Call ComputePatch on all the Difference objects in 'diffs'."""
981 print len(diffs), "diffs to compute"
982
983 # Do the largest files first, to try and reduce the long-pole effect.
984 by_size = [(i.tf.size, i) for i in diffs]
985 by_size.sort(reverse=True)
986 by_size = [i[1] for i in by_size]
987
988 lock = threading.Lock()
989 diff_iter = iter(by_size) # accessed under lock
990
991 def worker():
992 try:
993 lock.acquire()
994 for d in diff_iter:
995 lock.release()
996 start = time.time()
997 d.ComputePatch()
998 dur = time.time() - start
999 lock.acquire()
1000
1001 tf, sf, patch = d.GetPatch()
1002 if sf.name == tf.name:
1003 name = tf.name
1004 else:
1005 name = "%s (%s)" % (tf.name, sf.name)
1006 if patch is None:
1007 print "patching failed! %s" % (name,)
1008 else:
1009 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1010 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1011 lock.release()
1012 except Exception, e:
1013 print e
1014 raise
1015
1016 # start worker threads; wait for them all to finish.
1017 threads = [threading.Thread(target=worker)
1018 for i in range(OPTIONS.worker_threads)]
1019 for th in threads:
1020 th.start()
1021 while threads:
1022 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001023
1024
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001025class BlockDifference:
1026 def __init__(self, partition, tgt, src=None):
1027 self.tgt = tgt
1028 self.src = src
1029 self.partition = partition
1030
1031 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads)
1032 tmpdir = tempfile.mkdtemp()
1033 OPTIONS.tempfiles.append(tmpdir)
1034 self.path = os.path.join(tmpdir, partition)
1035 b.Compute(self.path)
1036
1037 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1038
1039 def WriteScript(self, script, output_zip, progress=None):
1040 if not self.src:
1041 # write the output unconditionally
1042 if progress: script.ShowProgress(progress, 0)
1043 self._WriteUpdate(script, output_zip)
1044
1045 else:
1046 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1047 (self.device, self.src.care_map.to_string_raw(),
1048 self.src.TotalSha1()))
1049 script.Print("Patching %s image..." % (self.partition,))
1050 if progress: script.ShowProgress(progress, 0)
1051 self._WriteUpdate(script, output_zip)
1052 script.AppendExtra(('else\n'
1053 ' (range_sha1("%s", "%s") == "%s") ||\n'
1054 ' abort("%s partition has unexpected contents");\n'
1055 'endif;') %
1056 (self.device, self.tgt.care_map.to_string_raw(),
1057 self.tgt.TotalSha1(), self.partition))
1058
1059 def _WriteUpdate(self, script, output_zip):
1060 partition = self.partition
1061 with open(self.path + ".transfer.list", "rb") as f:
1062 ZipWriteStr(output_zip, partition + ".transfer.list", f.read())
1063 with open(self.path + ".new.dat", "rb") as f:
1064 ZipWriteStr(output_zip, partition + ".new.dat", f.read())
1065 with open(self.path + ".patch.dat", "rb") as f:
1066 ZipWriteStr(output_zip, partition + ".patch.dat", f.read(),
1067 compression=zipfile.ZIP_STORED)
1068
1069 call = (('block_image_update("%s", '
1070 'package_extract_file("%s.transfer.list"), '
1071 '"%s.new.dat", "%s.patch.dat");\n') %
1072 (self.device, partition, partition, partition))
1073 script.AppendExtra(script._WordWrap(call))
1074
1075
1076DataImage = blockimgdiff.DataImage
1077
1078
Doug Zongker96a57e72010-09-26 14:57:41 -07001079# map recovery.fstab's fs_types to mount/format "partition types"
1080PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001081 "ext4": "EMMC", "emmc": "EMMC",
1082 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001083
1084def GetTypeAndDevice(mount_point, info):
1085 fstab = info["fstab"]
1086 if fstab:
1087 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1088 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001089 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001090
1091
1092def ParseCertificate(data):
1093 """Parse a PEM-format certificate."""
1094 cert = []
1095 save = False
1096 for line in data.split("\n"):
1097 if "--END CERTIFICATE--" in line:
1098 break
1099 if save:
1100 cert.append(line)
1101 if "--BEGIN CERTIFICATE--" in line:
1102 save = True
1103 cert = "".join(cert).decode('base64')
1104 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001105
Doug Zongker412c02f2014-02-13 10:58:24 -08001106def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1107 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001108 """Generate a binary patch that creates the recovery image starting
1109 with the boot image. (Most of the space in these images is just the
1110 kernel, which is identical for the two, so the resulting patch
1111 should be efficient.) Add it to the output zip, along with a shell
1112 script that is run from init.rc on first boot to actually do the
1113 patching and install the new recovery image.
1114
1115 recovery_img and boot_img should be File objects for the
1116 corresponding images. info should be the dictionary returned by
1117 common.LoadInfoDict() on the input target_files.
1118 """
1119
Doug Zongker412c02f2014-02-13 10:58:24 -08001120 if info_dict is None:
1121 info_dict = OPTIONS.info_dict
1122
Doug Zongkerc9253822014-02-04 12:17:58 -08001123 diff_program = ["imgdiff"]
1124 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1125 if os.path.exists(path):
1126 diff_program.append("-b")
1127 diff_program.append(path)
1128 bonus_args = "-b /system/etc/recovery-resource.dat"
1129 else:
1130 bonus_args = ""
1131
1132 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1133 _, _, patch = d.ComputePatch()
1134 output_sink("recovery-from-boot.p", patch)
1135
Ying Wanga961a092014-07-29 11:42:37 -07001136 td_pair = GetTypeAndDevice("/boot", info_dict)
1137 if not td_pair:
1138 return
1139 boot_type, boot_device = td_pair
1140 td_pair = GetTypeAndDevice("/recovery", info_dict)
1141 if not td_pair:
1142 return
1143 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001144
1145 sh = """#!/system/bin/sh
1146if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1147 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"
1148else
1149 log -t recovery "Recovery image already installed"
1150fi
1151""" % { 'boot_size': boot_img.size,
1152 'boot_sha1': boot_img.sha1,
1153 'recovery_size': recovery_img.size,
1154 'recovery_sha1': recovery_img.sha1,
1155 'boot_type': boot_type,
1156 'boot_device': boot_device,
1157 'recovery_type': recovery_type,
1158 'recovery_device': recovery_device,
1159 'bonus_args': bonus_args,
1160 }
1161
1162 # The install script location moved from /system/etc to /system/bin
1163 # in the L release. Parse the init.rc file to find out where the
1164 # target-files expects it to be, and put it there.
1165 sh_location = "etc/install-recovery.sh"
1166 try:
1167 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1168 for line in f:
1169 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1170 if m:
1171 sh_location = m.group(1)
1172 print "putting script in", sh_location
1173 break
1174 except (OSError, IOError), e:
1175 print "failed to read init.rc: %s" % (e,)
1176
1177 output_sink(sh_location, sh)