blob: ffb6e39d98e85c99046700ae7a4990a9f150c718 [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 Zongker602a84e2009-06-18 08:35:12 -070045OPTIONS.search_path = "out/host/linux-x86"
T.R. Fullhart37e10522013-03-18 10:31:26 -070046OPTIONS.signapk_path = "framework/signapk.jar" # Relative to search_path
47OPTIONS.extra_signapk_args = []
48OPTIONS.java_path = "java" # Use the one on the path by default.
Baligh Uddin339ee492014-09-05 11:18:07 -070049OPTIONS.java_args = "-Xmx2048m" # JVM Args
T.R. Fullhart37e10522013-03-18 10:31:26 -070050OPTIONS.public_key_suffix = ".x509.pem"
51OPTIONS.private_key_suffix = ".pk8"
Doug Zongkereef39442009-04-02 12:14:19 -070052OPTIONS.verbose = False
53OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070054OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080055OPTIONS.extras = {}
Doug Zongkerc77a9ad2010-09-16 11:28:43 -070056OPTIONS.info_dict = None
Doug Zongkereef39442009-04-02 12:14:19 -070057
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080058
59# Values for "certificate" in apkcerts that mean special things.
60SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
61
62
Doug Zongkereef39442009-04-02 12:14:19 -070063class ExternalError(RuntimeError): pass
64
65
66def Run(args, **kwargs):
67 """Create and return a subprocess.Popen object, printing the command
68 line on the terminal if -v was specified."""
69 if OPTIONS.verbose:
70 print " running: ", " ".join(args)
71 return subprocess.Popen(args, **kwargs)
72
73
Ying Wang7e6d4e42010-12-13 16:25:36 -080074def CloseInheritedPipes():
75 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
76 before doing other work."""
77 if platform.system() != "Darwin":
78 return
79 for d in range(3, 1025):
80 try:
81 stat = os.fstat(d)
82 if stat is not None:
83 pipebit = stat[0] & 0x1000
84 if pipebit != 0:
85 os.close(d)
86 except OSError:
87 pass
88
89
Doug Zongkerc9253822014-02-04 12:17:58 -080090def LoadInfoDict(input):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070091 """Read and parse the META/misc_info.txt key/value pairs from the
92 input target files and return a dict."""
93
Doug Zongkerc9253822014-02-04 12:17:58 -080094 def read_helper(fn):
95 if isinstance(input, zipfile.ZipFile):
96 return input.read(fn)
97 else:
98 path = os.path.join(input, *fn.split("/"))
99 try:
100 with open(path) as f:
101 return f.read()
102 except IOError, e:
103 if e.errno == errno.ENOENT:
104 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700105 d = {}
106 try:
Michael Runge6e836112014-04-15 17:40:21 -0700107 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700108 except KeyError:
109 # ok if misc_info.txt doesn't exist
110 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700111
Doug Zongker37974732010-09-16 17:44:38 -0700112 # backwards compatibility: These values used to be in their own
113 # files. Look for them, in case we're processing an old
114 # target_files zip.
115
116 if "mkyaffs2_extra_flags" not in d:
117 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800118 d["mkyaffs2_extra_flags"] = read_helper("META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700119 except KeyError:
120 # ok if flags don't exist
121 pass
122
123 if "recovery_api_version" not in d:
124 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800125 d["recovery_api_version"] = read_helper("META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700126 except KeyError:
127 raise ValueError("can't find recovery API version in input target-files")
128
129 if "tool_extensions" not in d:
130 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800131 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700132 except KeyError:
133 # ok if extensions don't exist
134 pass
135
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800136 if "fstab_version" not in d:
137 d["fstab_version"] = "1"
138
Doug Zongker37974732010-09-16 17:44:38 -0700139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700141 for line in data.split("\n"):
142 if not line: continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700143 name, value = line.split(" ", 1)
144 if not value: continue
Doug Zongker37974732010-09-16 17:44:38 -0700145 if name == "blocksize":
146 d[name] = value
147 else:
148 d[name + "_size"] = value
149 except KeyError:
150 pass
151
152 def makeint(key):
153 if key in d:
154 d[key] = int(d[key], 0)
155
156 makeint("recovery_api_version")
157 makeint("blocksize")
158 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700159 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700160 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700161 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700162 makeint("recovery_size")
163 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800164 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700165
Doug Zongkerc9253822014-02-04 12:17:58 -0800166 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
167 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700168 return d
169
Doug Zongkerc9253822014-02-04 12:17:58 -0800170def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700171 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800172 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700173 except KeyError:
174 print "Warning: could not find SYSTEM/build.prop in %s" % zip
175 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700176 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700177
Michael Runge6e836112014-04-15 17:40:21 -0700178def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700180 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700181 line = line.strip()
182 if not line or line.startswith("#"): continue
Ying Wang114b46f2014-04-15 11:24:00 -0700183 if "=" in line:
184 name, value = line.split("=", 1)
185 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700186 return d
187
Doug Zongkerc9253822014-02-04 12:17:58 -0800188def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700189 class Partition(object):
190 pass
191
192 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800193 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700194 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800195 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700196 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700197
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800198 if fstab_version == 1:
199 d = {}
200 for line in data.split("\n"):
201 line = line.strip()
202 if not line or line.startswith("#"): continue
203 pieces = line.split()
204 if not (3 <= len(pieces) <= 4):
205 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700206
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800207 p = Partition()
208 p.mount_point = pieces[0]
209 p.fs_type = pieces[1]
210 p.device = pieces[2]
211 p.length = 0
212 options = None
213 if len(pieces) >= 4:
214 if pieces[3].startswith("/"):
215 p.device2 = pieces[3]
216 if len(pieces) >= 5:
217 options = pieces[4]
218 else:
219 p.device2 = None
220 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800221 else:
222 p.device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700223
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 if options:
225 options = options.split(",")
226 for i in options:
227 if i.startswith("length="):
228 p.length = int(i[7:])
229 else:
230 print "%s: unknown option \"%s\"" % (p.mount_point, i)
231
232 d[p.mount_point] = p
233
234 elif fstab_version == 2:
235 d = {}
236 for line in data.split("\n"):
237 line = line.strip()
238 if not line or line.startswith("#"): continue
239 pieces = line.split()
240 if len(pieces) != 5:
241 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
242
243 # Ignore entries that are managed by vold
244 options = pieces[4]
245 if "voldmanaged=" in options: continue
246
247 # It's a good line, parse it
248 p = Partition()
249 p.device = pieces[0]
250 p.mount_point = pieces[1]
251 p.fs_type = pieces[2]
252 p.device2 = None
253 p.length = 0
254
Doug Zongker086cbb02011-02-17 15:54:20 -0800255 options = options.split(",")
256 for i in options:
257 if i.startswith("length="):
258 p.length = int(i[7:])
259 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800260 # Ignore all unknown options in the unified fstab
261 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800262
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 d[p.mount_point] = p
264
265 else:
266 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
267
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700268 return d
269
270
Doug Zongker37974732010-09-16 17:44:38 -0700271def DumpInfoDict(d):
272 for k, v in sorted(d.items()):
273 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700274
Doug Zongkerd5131602012-08-02 14:46:42 -0700275def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700276 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700277 'sourcedir'), and turn them into a boot image. Return the image
278 data, or None if sourcedir does not appear to contains files for
279 building the requested image."""
280
281 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
282 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
283 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700284
Doug Zongkerd5131602012-08-02 14:46:42 -0700285 if info_dict is None:
286 info_dict = OPTIONS.info_dict
287
Doug Zongkereef39442009-04-02 12:14:19 -0700288 ramdisk_img = tempfile.NamedTemporaryFile()
289 img = tempfile.NamedTemporaryFile()
290
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700291 if os.access(fs_config_file, os.F_OK):
292 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
293 else:
294 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
295 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700296 p2 = Run(["minigzip"],
297 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700298
299 p2.wait()
300 p1.wait()
301 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700302 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700303
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800304 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
305 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
306
307 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700308
Benoit Fradina45a8682014-07-14 21:00:43 +0200309 fn = os.path.join(sourcedir, "second")
310 if os.access(fn, os.F_OK):
311 cmd.append("--second")
312 cmd.append(fn)
313
Doug Zongker171f1cd2009-06-15 22:36:37 -0700314 fn = os.path.join(sourcedir, "cmdline")
315 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700316 cmd.append("--cmdline")
317 cmd.append(open(fn).read().rstrip("\n"))
318
319 fn = os.path.join(sourcedir, "base")
320 if os.access(fn, os.F_OK):
321 cmd.append("--base")
322 cmd.append(open(fn).read().rstrip("\n"))
323
Ying Wang4de6b5b2010-08-25 14:29:34 -0700324 fn = os.path.join(sourcedir, "pagesize")
325 if os.access(fn, os.F_OK):
326 cmd.append("--pagesize")
327 cmd.append(open(fn).read().rstrip("\n"))
328
Doug Zongkerd5131602012-08-02 14:46:42 -0700329 args = info_dict.get("mkbootimg_args", None)
330 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700331 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700332
Doug Zongker38a649f2009-06-17 09:07:09 -0700333 cmd.extend(["--ramdisk", ramdisk_img.name,
334 "--output", img.name])
335
336 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700337 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700338 assert p.returncode == 0, "mkbootimg of %s image failed" % (
339 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700341 if info_dict.get("verity_key", None):
342 path = "/" + os.path.basename(sourcedir).lower()
343 cmd = ["boot_signer", path, img.name, info_dict["verity_key"], img.name]
344 p = Run(cmd, stdout=subprocess.PIPE)
345 p.communicate()
346 assert p.returncode == 0, "boot_signer of %s image failed" % path
347
Doug Zongkereef39442009-04-02 12:14:19 -0700348 img.seek(os.SEEK_SET, 0)
349 data = img.read()
350
351 ramdisk_img.close()
352 img.close()
353
354 return data
355
356
Doug Zongkerd5131602012-08-02 14:46:42 -0700357def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
358 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800359 """Return a File object (with name 'name') with the desired bootable
360 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700361 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
362 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800363 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Doug Zongker55d93282011-01-25 17:03:34 -0800365 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
366 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700367 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800368 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700369
370 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
371 if os.path.exists(prebuilt_path):
372 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
373 return File.FromLocalFile(name, prebuilt_path)
374
375 print "building image from target_files %s..." % (tree_subdir,)
376 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
377 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
378 os.path.join(unpack_dir, fs_config),
379 info_dict)
380 if data:
381 return File(name, data)
382 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800383
Doug Zongkereef39442009-04-02 12:14:19 -0700384
Doug Zongker75f17362009-12-08 13:46:44 -0800385def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800386 """Unzip the given archive into a temporary directory and return the name.
387
388 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
389 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
390
391 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
392 main file), open for reading.
393 """
Doug Zongkereef39442009-04-02 12:14:19 -0700394
395 tmp = tempfile.mkdtemp(prefix="targetfiles-")
396 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800397
398 def unzip_to_dir(filename, dirname):
399 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
400 if pattern is not None:
401 cmd.append(pattern)
402 p = Run(cmd, stdout=subprocess.PIPE)
403 p.communicate()
404 if p.returncode != 0:
405 raise ExternalError("failed to unzip input target-files \"%s\"" %
406 (filename,))
407
408 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
409 if m:
410 unzip_to_dir(m.group(1), tmp)
411 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
412 filename = m.group(1)
413 else:
414 unzip_to_dir(filename, tmp)
415
416 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700417
418
419def GetKeyPasswords(keylist):
420 """Given a list of keys, prompt the user to enter passwords for
421 those which require them. Return a {key: password} dict. password
422 will be None if the key has no password."""
423
Doug Zongker8ce7c252009-05-22 13:34:54 -0700424 no_passwords = []
425 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700426 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700427 devnull = open("/dev/null", "w+b")
428 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800429 # We don't need a password for things that aren't really keys.
430 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700431 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700432 continue
433
T.R. Fullhart37e10522013-03-18 10:31:26 -0700434 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700435 "-inform", "DER", "-nocrypt"],
436 stdin=devnull.fileno(),
437 stdout=devnull.fileno(),
438 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700439 p.communicate()
440 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700441 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700442 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700443 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700444 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
445 "-inform", "DER", "-passin", "pass:"],
446 stdin=devnull.fileno(),
447 stdout=devnull.fileno(),
448 stderr=subprocess.PIPE)
449 stdout, stderr = p.communicate()
450 if p.returncode == 0:
451 # Encrypted key with empty string as password.
452 key_passwords[k] = ''
453 elif stderr.startswith('Error decrypting key'):
454 # Definitely encrypted key.
455 # It would have said "Error reading key" if it didn't parse correctly.
456 need_passwords.append(k)
457 else:
458 # Potentially, a type of key that openssl doesn't understand.
459 # We'll let the routines in signapk.jar handle it.
460 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700461 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700462
T.R. Fullhart37e10522013-03-18 10:31:26 -0700463 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700464 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700465 return key_passwords
466
467
Doug Zongker951495f2009-08-14 12:44:19 -0700468def SignFile(input_name, output_name, key, password, align=None,
469 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700470 """Sign the input_name zip/jar/apk, producing output_name. Use the
471 given key and password (the latter may be None if the key does not
472 have a password.
473
474 If align is an integer > 1, zipalign is run to align stored files in
475 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700476
477 If whole_file is true, use the "-w" option to SignApk to embed a
478 signature that covers the whole file in the archive comment of the
479 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700480 """
Doug Zongker951495f2009-08-14 12:44:19 -0700481
Doug Zongkereef39442009-04-02 12:14:19 -0700482 if align == 0 or align == 1:
483 align = None
484
485 if align:
486 temp = tempfile.NamedTemporaryFile()
487 sign_name = temp.name
488 else:
489 sign_name = output_name
490
Baligh Uddin339ee492014-09-05 11:18:07 -0700491 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700492 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
493 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700494 if whole_file:
495 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700496 cmd.extend([key + OPTIONS.public_key_suffix,
497 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700498 input_name, sign_name])
499
500 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700501 if password is not None:
502 password += "\n"
503 p.communicate(password)
504 if p.returncode != 0:
505 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
506
507 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700508 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700509 p.communicate()
510 if p.returncode != 0:
511 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
512 temp.close()
513
514
Doug Zongker37974732010-09-16 17:44:38 -0700515def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700516 """Check the data string passed against the max size limit, if
517 any, for the given target. Raise exception if the data is too big.
518 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700519
Doug Zongker1684d9c2010-09-17 07:44:38 -0700520 if target.endswith(".img"): target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700521 mount_point = "/" + target
522
Ying Wangf8824af2014-06-03 14:07:27 -0700523 fs_type = None
524 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700525 if info_dict["fstab"]:
526 if mount_point == "/userdata": mount_point = "/data"
527 p = info_dict["fstab"][mount_point]
528 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800529 device = p.device
530 if "/" in device:
531 device = device[device.rfind("/")+1:]
532 limit = info_dict.get(device + "_size", None)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700533 if not fs_type or not limit: return
Doug Zongkereef39442009-04-02 12:14:19 -0700534
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700535 if fs_type == "yaffs2":
536 # image size should be increased by 1/64th to account for the
537 # spare area (64 bytes per 2k page)
538 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800539 size = len(data)
540 pct = float(size) * 100.0 / limit
541 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
542 if pct >= 99.0:
543 raise ExternalError(msg)
544 elif pct >= 95.0:
545 print
546 print " WARNING: ", msg
547 print
548 elif OPTIONS.verbose:
549 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700550
551
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800552def ReadApkCerts(tf_zip):
553 """Given a target_files ZipFile, parse the META/apkcerts.txt file
554 and return a {package: cert} dict."""
555 certmap = {}
556 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
557 line = line.strip()
558 if not line: continue
559 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
560 r'private_key="(.*)"$', line)
561 if m:
562 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700563 public_key_suffix_len = len(OPTIONS.public_key_suffix)
564 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800565 if cert in SPECIAL_CERT_STRINGS and not privkey:
566 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700567 elif (cert.endswith(OPTIONS.public_key_suffix) and
568 privkey.endswith(OPTIONS.private_key_suffix) and
569 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
570 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800571 else:
572 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
573 return certmap
574
575
Doug Zongkereef39442009-04-02 12:14:19 -0700576COMMON_DOCSTRING = """
577 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700578 Prepend <dir>/bin to the list of places to search for binaries
579 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Doug Zongker05d3dea2009-06-22 11:32:31 -0700581 -s (--device_specific) <file>
582 Path to the python module containing device-specific
583 releasetools code.
584
Doug Zongker8bec09e2009-11-30 15:37:14 -0800585 -x (--extra) <key=value>
586 Add a key/value pair to the 'extras' dict, which device-specific
587 extension code may look at.
588
Doug Zongkereef39442009-04-02 12:14:19 -0700589 -v (--verbose)
590 Show command lines being executed.
591
592 -h (--help)
593 Display this usage message and exit.
594"""
595
596def Usage(docstring):
597 print docstring.rstrip("\n")
598 print COMMON_DOCSTRING
599
600
601def ParseOptions(argv,
602 docstring,
603 extra_opts="", extra_long_opts=(),
604 extra_option_handler=None):
605 """Parse the options in argv and return any arguments that aren't
606 flags. docstring is the calling module's docstring, to be displayed
607 for errors and -h. extra_opts and extra_long_opts are for flags
608 defined by the caller, which are processed by passing them to
609 extra_option_handler."""
610
611 try:
612 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800613 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700614 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
615 "java_path=", "public_key_suffix=", "private_key_suffix=",
616 "device_specific=", "extra="] +
617 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700618 except getopt.GetoptError, err:
619 Usage(docstring)
620 print "**", str(err), "**"
621 sys.exit(2)
622
623 path_specified = False
624
625 for o, a in opts:
626 if o in ("-h", "--help"):
627 Usage(docstring)
628 sys.exit()
629 elif o in ("-v", "--verbose"):
630 OPTIONS.verbose = True
631 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700632 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700633 elif o in ("--signapk_path",):
634 OPTIONS.signapk_path = a
635 elif o in ("--extra_signapk_args",):
636 OPTIONS.extra_signapk_args = shlex.split(a)
637 elif o in ("--java_path",):
638 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700639 elif o in ("--java_args",):
640 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700641 elif o in ("--public_key_suffix",):
642 OPTIONS.public_key_suffix = a
643 elif o in ("--private_key_suffix",):
644 OPTIONS.private_key_suffix = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700645 elif o in ("-s", "--device_specific"):
646 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800647 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800648 key, value = a.split("=", 1)
649 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700650 else:
651 if extra_option_handler is None or not extra_option_handler(o, a):
652 assert False, "unknown option \"%s\"" % (o,)
653
Doug Zongker602a84e2009-06-18 08:35:12 -0700654 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
655 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700656
657 return args
658
659
Doug Zongkerfc44a512014-08-26 13:10:25 -0700660def MakeTempFile(prefix=None, suffix=None):
661 """Make a temp file and add it to the list of things to be deleted
662 when Cleanup() is called. Return the filename."""
663 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
664 os.close(fd)
665 OPTIONS.tempfiles.append(fn)
666 return fn
667
668
Doug Zongkereef39442009-04-02 12:14:19 -0700669def Cleanup():
670 for i in OPTIONS.tempfiles:
671 if os.path.isdir(i):
672 shutil.rmtree(i)
673 else:
674 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700675
676
677class PasswordManager(object):
678 def __init__(self):
679 self.editor = os.getenv("EDITOR", None)
680 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
681
682 def GetPasswords(self, items):
683 """Get passwords corresponding to each string in 'items',
684 returning a dict. (The dict may have keys in addition to the
685 values in 'items'.)
686
687 Uses the passwords in $ANDROID_PW_FILE if available, letting the
688 user edit that file to add more needed passwords. If no editor is
689 available, or $ANDROID_PW_FILE isn't define, prompts the user
690 interactively in the ordinary way.
691 """
692
693 current = self.ReadFile()
694
695 first = True
696 while True:
697 missing = []
698 for i in items:
699 if i not in current or not current[i]:
700 missing.append(i)
701 # Are all the passwords already in the file?
702 if not missing: return current
703
704 for i in missing:
705 current[i] = ""
706
707 if not first:
708 print "key file %s still missing some passwords." % (self.pwfile,)
709 answer = raw_input("try to edit again? [y]> ").strip()
710 if answer and answer[0] not in 'yY':
711 raise RuntimeError("key passwords unavailable")
712 first = False
713
714 current = self.UpdateAndReadFile(current)
715
716 def PromptResult(self, current):
717 """Prompt the user to enter a value (password) for each key in
718 'current' whose value is fales. Returns a new dict with all the
719 values.
720 """
721 result = {}
722 for k, v in sorted(current.iteritems()):
723 if v:
724 result[k] = v
725 else:
726 while True:
727 result[k] = getpass.getpass("Enter password for %s key> "
728 % (k,)).strip()
729 if result[k]: break
730 return result
731
732 def UpdateAndReadFile(self, current):
733 if not self.editor or not self.pwfile:
734 return self.PromptResult(current)
735
736 f = open(self.pwfile, "w")
737 os.chmod(self.pwfile, 0600)
738 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
739 f.write("# (Additional spaces are harmless.)\n\n")
740
741 first_line = None
742 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
743 sorted.sort()
744 for i, (_, k, v) in enumerate(sorted):
745 f.write("[[[ %s ]]] %s\n" % (v, k))
746 if not v and first_line is None:
747 # position cursor on first line with no password.
748 first_line = i + 4
749 f.close()
750
751 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
752 _, _ = p.communicate()
753
754 return self.ReadFile()
755
756 def ReadFile(self):
757 result = {}
758 if self.pwfile is None: return result
759 try:
760 f = open(self.pwfile, "r")
761 for line in f:
762 line = line.strip()
763 if not line or line[0] == '#': continue
764 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
765 if not m:
766 print "failed to parse password file: ", line
767 else:
768 result[m.group(2)] = m.group(1)
769 f.close()
770 except IOError, e:
771 if e.errno != errno.ENOENT:
772 print "error reading password file: ", str(e)
773 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700774
775
Geremy Condra36bd3652014-02-06 19:45:10 -0800776def ZipWriteStr(zip, filename, data, perms=0644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700777 # use a fixed timestamp so the output is repeatable.
778 zinfo = zipfile.ZipInfo(filename=filename,
779 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800780 if compression is None:
781 zinfo.compress_type = zip.compression
782 else:
783 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700784 zinfo.external_attr = perms << 16
785 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700786
787
788class DeviceSpecificParams(object):
789 module = None
790 def __init__(self, **kwargs):
791 """Keyword arguments to the constructor become attributes of this
792 object, which is passed to all functions in the device-specific
793 module."""
794 for k, v in kwargs.iteritems():
795 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800796 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700797
798 if self.module is None:
799 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700800 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700801 try:
802 if os.path.isdir(path):
803 info = imp.find_module("releasetools", [path])
804 else:
805 d, f = os.path.split(path)
806 b, x = os.path.splitext(f)
807 if x == ".py":
808 f = b
809 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800810 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700811 self.module = imp.load_module("device_specific", *info)
812 except ImportError:
813 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700814
815 def _DoCall(self, function_name, *args, **kwargs):
816 """Call the named function in the device-specific module, passing
817 the given args and kwargs. The first argument to the call will be
818 the DeviceSpecific object itself. If there is no module, or the
819 module does not define the function, return the value of the
820 'default' kwarg (which itself defaults to None)."""
821 if self.module is None or not hasattr(self.module, function_name):
822 return kwargs.get("default", None)
823 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
824
825 def FullOTA_Assertions(self):
826 """Called after emitting the block of assertions at the top of a
827 full OTA package. Implementations can add whatever additional
828 assertions they like."""
829 return self._DoCall("FullOTA_Assertions")
830
Doug Zongkere5ff5902012-01-17 10:55:37 -0800831 def FullOTA_InstallBegin(self):
832 """Called at the start of full OTA installation."""
833 return self._DoCall("FullOTA_InstallBegin")
834
Doug Zongker05d3dea2009-06-22 11:32:31 -0700835 def FullOTA_InstallEnd(self):
836 """Called at the end of full OTA installation; typically this is
837 used to install the image for the device's baseband processor."""
838 return self._DoCall("FullOTA_InstallEnd")
839
840 def IncrementalOTA_Assertions(self):
841 """Called after emitting the block of assertions at the top of an
842 incremental OTA package. Implementations can add whatever
843 additional assertions they like."""
844 return self._DoCall("IncrementalOTA_Assertions")
845
Doug Zongkere5ff5902012-01-17 10:55:37 -0800846 def IncrementalOTA_VerifyBegin(self):
847 """Called at the start of the verification phase of incremental
848 OTA installation; additional checks can be placed here to abort
849 the script before any changes are made."""
850 return self._DoCall("IncrementalOTA_VerifyBegin")
851
Doug Zongker05d3dea2009-06-22 11:32:31 -0700852 def IncrementalOTA_VerifyEnd(self):
853 """Called at the end of the verification phase of incremental OTA
854 installation; additional checks can be placed here to abort the
855 script before any changes are made."""
856 return self._DoCall("IncrementalOTA_VerifyEnd")
857
Doug Zongkere5ff5902012-01-17 10:55:37 -0800858 def IncrementalOTA_InstallBegin(self):
859 """Called at the start of incremental OTA installation (after
860 verification is complete)."""
861 return self._DoCall("IncrementalOTA_InstallBegin")
862
Doug Zongker05d3dea2009-06-22 11:32:31 -0700863 def IncrementalOTA_InstallEnd(self):
864 """Called at the end of incremental OTA installation; typically
865 this is used to install the image for the device's baseband
866 processor."""
867 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700868
869class File(object):
870 def __init__(self, name, data):
871 self.name = name
872 self.data = data
873 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800874 self.sha1 = sha1(data).hexdigest()
875
876 @classmethod
877 def FromLocalFile(cls, name, diskname):
878 f = open(diskname, "rb")
879 data = f.read()
880 f.close()
881 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700882
883 def WriteToTemp(self):
884 t = tempfile.NamedTemporaryFile()
885 t.write(self.data)
886 t.flush()
887 return t
888
Geremy Condra36bd3652014-02-06 19:45:10 -0800889 def AddToZip(self, z, compression=None):
890 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700891
892DIFF_PROGRAM_BY_EXT = {
893 ".gz" : "imgdiff",
894 ".zip" : ["imgdiff", "-z"],
895 ".jar" : ["imgdiff", "-z"],
896 ".apk" : ["imgdiff", "-z"],
897 ".img" : "imgdiff",
898 }
899
900class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700901 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700902 self.tf = tf
903 self.sf = sf
904 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700905 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700906
907 def ComputePatch(self):
908 """Compute the patch (as a string of data) needed to turn sf into
909 tf. Returns the same tuple as GetPatch()."""
910
911 tf = self.tf
912 sf = self.sf
913
Doug Zongker24cd2802012-08-14 16:36:15 -0700914 if self.diff_program:
915 diff_program = self.diff_program
916 else:
917 ext = os.path.splitext(tf.name)[1]
918 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700919
920 ttemp = tf.WriteToTemp()
921 stemp = sf.WriteToTemp()
922
923 ext = os.path.splitext(tf.name)[1]
924
925 try:
926 ptemp = tempfile.NamedTemporaryFile()
927 if isinstance(diff_program, list):
928 cmd = copy.copy(diff_program)
929 else:
930 cmd = [diff_program]
931 cmd.append(stemp.name)
932 cmd.append(ttemp.name)
933 cmd.append(ptemp.name)
934 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -0700935 err = []
936 def run():
937 _, e = p.communicate()
938 if e: err.append(e)
939 th = threading.Thread(target=run)
940 th.start()
941 th.join(timeout=300) # 5 mins
942 if th.is_alive():
943 print "WARNING: diff command timed out"
944 p.terminate()
945 th.join(5)
946 if th.is_alive():
947 p.kill()
948 th.join()
949
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700950 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -0700951 print "WARNING: failure running %s:\n%s\n" % (
952 diff_program, "".join(err))
953 self.patch = None
954 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700955 diff = ptemp.read()
956 finally:
957 ptemp.close()
958 stemp.close()
959 ttemp.close()
960
961 self.patch = diff
962 return self.tf, self.sf, self.patch
963
964
965 def GetPatch(self):
966 """Return a tuple (target_file, source_file, patch_data).
967 patch_data may be None if ComputePatch hasn't been called, or if
968 computing the patch failed."""
969 return self.tf, self.sf, self.patch
970
971
972def ComputeDifferences(diffs):
973 """Call ComputePatch on all the Difference objects in 'diffs'."""
974 print len(diffs), "diffs to compute"
975
976 # Do the largest files first, to try and reduce the long-pole effect.
977 by_size = [(i.tf.size, i) for i in diffs]
978 by_size.sort(reverse=True)
979 by_size = [i[1] for i in by_size]
980
981 lock = threading.Lock()
982 diff_iter = iter(by_size) # accessed under lock
983
984 def worker():
985 try:
986 lock.acquire()
987 for d in diff_iter:
988 lock.release()
989 start = time.time()
990 d.ComputePatch()
991 dur = time.time() - start
992 lock.acquire()
993
994 tf, sf, patch = d.GetPatch()
995 if sf.name == tf.name:
996 name = tf.name
997 else:
998 name = "%s (%s)" % (tf.name, sf.name)
999 if patch is None:
1000 print "patching failed! %s" % (name,)
1001 else:
1002 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1003 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1004 lock.release()
1005 except Exception, e:
1006 print e
1007 raise
1008
1009 # start worker threads; wait for them all to finish.
1010 threads = [threading.Thread(target=worker)
1011 for i in range(OPTIONS.worker_threads)]
1012 for th in threads:
1013 th.start()
1014 while threads:
1015 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001016
1017
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001018class BlockDifference:
1019 def __init__(self, partition, tgt, src=None):
1020 self.tgt = tgt
1021 self.src = src
1022 self.partition = partition
1023
1024 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads)
1025 tmpdir = tempfile.mkdtemp()
1026 OPTIONS.tempfiles.append(tmpdir)
1027 self.path = os.path.join(tmpdir, partition)
1028 b.Compute(self.path)
1029
1030 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1031
1032 def WriteScript(self, script, output_zip, progress=None):
1033 if not self.src:
1034 # write the output unconditionally
1035 if progress: script.ShowProgress(progress, 0)
1036 self._WriteUpdate(script, output_zip)
1037
1038 else:
1039 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' %
1040 (self.device, self.src.care_map.to_string_raw(),
1041 self.src.TotalSha1()))
1042 script.Print("Patching %s image..." % (self.partition,))
1043 if progress: script.ShowProgress(progress, 0)
1044 self._WriteUpdate(script, output_zip)
1045 script.AppendExtra(('else\n'
1046 ' (range_sha1("%s", "%s") == "%s") ||\n'
1047 ' abort("%s partition has unexpected contents");\n'
1048 'endif;') %
1049 (self.device, self.tgt.care_map.to_string_raw(),
1050 self.tgt.TotalSha1(), self.partition))
1051
1052 def _WriteUpdate(self, script, output_zip):
1053 partition = self.partition
1054 with open(self.path + ".transfer.list", "rb") as f:
1055 ZipWriteStr(output_zip, partition + ".transfer.list", f.read())
1056 with open(self.path + ".new.dat", "rb") as f:
1057 ZipWriteStr(output_zip, partition + ".new.dat", f.read())
1058 with open(self.path + ".patch.dat", "rb") as f:
1059 ZipWriteStr(output_zip, partition + ".patch.dat", f.read(),
1060 compression=zipfile.ZIP_STORED)
1061
1062 call = (('block_image_update("%s", '
1063 'package_extract_file("%s.transfer.list"), '
1064 '"%s.new.dat", "%s.patch.dat");\n') %
1065 (self.device, partition, partition, partition))
1066 script.AppendExtra(script._WordWrap(call))
1067
1068
1069DataImage = blockimgdiff.DataImage
1070
1071
Doug Zongker96a57e72010-09-26 14:57:41 -07001072# map recovery.fstab's fs_types to mount/format "partition types"
1073PARTITION_TYPES = { "yaffs2": "MTD", "mtd": "MTD",
JP Abgrall5bfed5a2014-06-16 14:17:40 -07001074 "ext4": "EMMC", "emmc": "EMMC",
1075 "f2fs": "EMMC" }
Doug Zongker96a57e72010-09-26 14:57:41 -07001076
1077def GetTypeAndDevice(mount_point, info):
1078 fstab = info["fstab"]
1079 if fstab:
1080 return PARTITION_TYPES[fstab[mount_point].fs_type], fstab[mount_point].device
1081 else:
Ying Wanga73b6562011-03-03 21:52:08 -08001082 return None
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001083
1084
1085def ParseCertificate(data):
1086 """Parse a PEM-format certificate."""
1087 cert = []
1088 save = False
1089 for line in data.split("\n"):
1090 if "--END CERTIFICATE--" in line:
1091 break
1092 if save:
1093 cert.append(line)
1094 if "--BEGIN CERTIFICATE--" in line:
1095 save = True
1096 cert = "".join(cert).decode('base64')
1097 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001098
Doug Zongker412c02f2014-02-13 10:58:24 -08001099def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1100 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001101 """Generate a binary patch that creates the recovery image starting
1102 with the boot image. (Most of the space in these images is just the
1103 kernel, which is identical for the two, so the resulting patch
1104 should be efficient.) Add it to the output zip, along with a shell
1105 script that is run from init.rc on first boot to actually do the
1106 patching and install the new recovery image.
1107
1108 recovery_img and boot_img should be File objects for the
1109 corresponding images. info should be the dictionary returned by
1110 common.LoadInfoDict() on the input target_files.
1111 """
1112
Doug Zongker412c02f2014-02-13 10:58:24 -08001113 if info_dict is None:
1114 info_dict = OPTIONS.info_dict
1115
Doug Zongkerc9253822014-02-04 12:17:58 -08001116 diff_program = ["imgdiff"]
1117 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1118 if os.path.exists(path):
1119 diff_program.append("-b")
1120 diff_program.append(path)
1121 bonus_args = "-b /system/etc/recovery-resource.dat"
1122 else:
1123 bonus_args = ""
1124
1125 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1126 _, _, patch = d.ComputePatch()
1127 output_sink("recovery-from-boot.p", patch)
1128
Ying Wanga961a092014-07-29 11:42:37 -07001129 td_pair = GetTypeAndDevice("/boot", info_dict)
1130 if not td_pair:
1131 return
1132 boot_type, boot_device = td_pair
1133 td_pair = GetTypeAndDevice("/recovery", info_dict)
1134 if not td_pair:
1135 return
1136 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001137
1138 sh = """#!/system/bin/sh
1139if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1140 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"
1141else
1142 log -t recovery "Recovery image already installed"
1143fi
1144""" % { 'boot_size': boot_img.size,
1145 'boot_sha1': boot_img.sha1,
1146 'recovery_size': recovery_img.size,
1147 'recovery_sha1': recovery_img.sha1,
1148 'boot_type': boot_type,
1149 'boot_device': boot_device,
1150 'recovery_type': recovery_type,
1151 'recovery_device': recovery_device,
1152 'bonus_args': bonus_args,
1153 }
1154
1155 # The install script location moved from /system/etc to /system/bin
1156 # in the L release. Parse the init.rc file to find out where the
1157 # target-files expects it to be, and put it there.
1158 sh_location = "etc/install-recovery.sh"
1159 try:
1160 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1161 for line in f:
1162 m = re.match("^service flash_recovery /system/(\S+)\s*$", line)
1163 if m:
1164 sh_location = m.group(1)
1165 print "putting script in", sh_location
1166 break
1167 except (OSError, IOError), e:
1168 print "failed to read init.rc: %s" % (e,)
1169
1170 output_sink(sh_location, sh)