blob: 9e5e70cba5570789788a7bccaa9fba0455538fa7 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Doug Zongker55d93282011-01-25 17:03:34 -080035try:
davidcad0bb92011-03-15 14:21:38 +000036 from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037except ImportError:
davidcad0bb92011-03-15 14:21:38 +000038 from sha import sha as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080039
Doug Zongkereef39442009-04-02 12:14:19 -070040
Dan Albert8b72aef2015-03-23 19:13:21 -070041class Options(object):
42 def __init__(self):
43 platform_search_path = {
44 "linux2": "out/host/linux-x86",
45 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070046 }
Doug Zongker85448772014-09-09 14:59:20 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048 self.search_path = platform_search_path.get(sys.platform, None)
49 self.signapk_path = "framework/signapk.jar" # Relative to search_path
50 self.extra_signapk_args = []
51 self.java_path = "java" # Use the one on the path by default.
52 self.java_args = "-Xmx2048m" # JVM Args
53 self.public_key_suffix = ".x509.pem"
54 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070055 # use otatools built boot_signer by default
56 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
62 self.worker_threads = None
63
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Dan Albert8b72aef2015-03-23 19:13:21 -0700100def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Doug Zongker37974732010-09-16 17:44:38 -0700151 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700153 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not line:
155 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700156 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if not value:
158 continue
Doug Zongker37974732010-09-16 17:44:38 -0700159 if name == "blocksize":
160 d[name] = value
161 else:
162 d[name + "_size"] = value
163 except KeyError:
164 pass
165
166 def makeint(key):
167 if key in d:
168 d[key] = int(d[key], 0)
169
170 makeint("recovery_api_version")
171 makeint("blocksize")
172 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700173 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700175 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("recovery_size")
177 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700179
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
181 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 except KeyError:
188 print "Warning: could not find SYSTEM/build.prop in %s" % zip
189 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700190 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191
Michael Runge6e836112014-04-15 17:40:21 -0700192def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700194 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 if not line or line.startswith("#"):
197 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700198 if "=" in line:
199 name, value = line.split("=", 1)
200 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700201 return d
202
Doug Zongkerc9253822014-02-04 12:17:58 -0800203def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204 class Partition(object):
Dan Albert8b72aef2015-03-23 19:13:21 -0700205 def __init__(self, mount_point, fs_type, device, length, device2):
206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700211
212 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800215 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700216 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700217
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800218 if fstab_version == 1:
219 d = {}
220 for line in data.split("\n"):
221 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not line or line.startswith("#"):
223 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800226 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 options = None
228 if len(pieces) >= 4:
229 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700230 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 if len(pieces) >= 5:
232 options = pieces[4]
233 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800235 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800236 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700238
Dan Albert8b72aef2015-03-23 19:13:21 -0700239 mount_point = pieces[0]
240 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800241 if options:
242 options = options.split(",")
243 for i in options:
244 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700247 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248
Dan Albert8b72aef2015-03-23 19:13:21 -0700249 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
250 device=pieces[2], length=length,
251 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252
253 elif fstab_version == 2:
254 d = {}
255 for line in data.split("\n"):
256 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700257 if not line or line.startswith("#"):
258 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259 pieces = line.split()
260 if len(pieces) != 5:
261 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
262
263 # Ignore entries that are managed by vold
264 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 if "voldmanaged=" in options:
266 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267
268 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800270 options = options.split(",")
271 for i in options:
272 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800274 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 # Ignore all unknown options in the unified fstab
276 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800277
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 mount_point = pieces[1]
279 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
280 device=pieces[0], length=length, device2=None)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281
282 else:
283 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
284
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700285 return d
286
287
Doug Zongker37974732010-09-16 17:44:38 -0700288def DumpInfoDict(d):
289 for k, v in sorted(d.items()):
290 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700291
Dan Albert8b72aef2015-03-23 19:13:21 -0700292
Doug Zongkerd5131602012-08-02 14:46:42 -0700293def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700294 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700295 'sourcedir'), and turn them into a boot image. Return the image
296 data, or None if sourcedir does not appear to contains files for
297 building the requested image."""
298
299 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
300 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
301 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303 if info_dict is None:
304 info_dict = OPTIONS.info_dict
305
Doug Zongkereef39442009-04-02 12:14:19 -0700306 ramdisk_img = tempfile.NamedTemporaryFile()
307 img = tempfile.NamedTemporaryFile()
308
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700309 if os.access(fs_config_file, os.F_OK):
310 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
311 else:
312 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
313 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700314 p2 = Run(["minigzip"],
315 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700316
317 p2.wait()
318 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
320 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700321
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800322 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
323 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
324
325 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700326
Benoit Fradina45a8682014-07-14 21:00:43 +0200327 fn = os.path.join(sourcedir, "second")
328 if os.access(fn, os.F_OK):
329 cmd.append("--second")
330 cmd.append(fn)
331
Doug Zongker171f1cd2009-06-15 22:36:37 -0700332 fn = os.path.join(sourcedir, "cmdline")
333 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700334 cmd.append("--cmdline")
335 cmd.append(open(fn).read().rstrip("\n"))
336
337 fn = os.path.join(sourcedir, "base")
338 if os.access(fn, os.F_OK):
339 cmd.append("--base")
340 cmd.append(open(fn).read().rstrip("\n"))
341
Ying Wang4de6b5b2010-08-25 14:29:34 -0700342 fn = os.path.join(sourcedir, "pagesize")
343 if os.access(fn, os.F_OK):
344 cmd.append("--pagesize")
345 cmd.append(open(fn).read().rstrip("\n"))
346
Doug Zongkerd5131602012-08-02 14:46:42 -0700347 args = info_dict.get("mkbootimg_args", None)
348 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700349 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700350
Doug Zongker38a649f2009-06-17 09:07:09 -0700351 cmd.extend(["--ramdisk", ramdisk_img.name,
352 "--output", img.name])
353
354 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700355 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700356 assert p.returncode == 0, "mkbootimg of %s image failed" % (
357 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700358
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700359 if info_dict.get("verity_key", None):
360 path = "/" + os.path.basename(sourcedir).lower()
Dan Albertcd9ecc02015-03-27 16:37:23 -0700361 cmd = [OPTIONS.boot_signer_path, path, img.name,
362 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700363 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700364 p = Run(cmd, stdout=subprocess.PIPE)
365 p.communicate()
366 assert p.returncode == 0, "boot_signer of %s image failed" % path
367
Doug Zongkereef39442009-04-02 12:14:19 -0700368 img.seek(os.SEEK_SET, 0)
369 data = img.read()
370
371 ramdisk_img.close()
372 img.close()
373
374 return data
375
376
Doug Zongkerd5131602012-08-02 14:46:42 -0700377def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
378 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800379 """Return a File object (with name 'name') with the desired bootable
380 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700381 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
382 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800383 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700384
Doug Zongker55d93282011-01-25 17:03:34 -0800385 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
386 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700387 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800388 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700389
390 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
391 if os.path.exists(prebuilt_path):
392 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
393 return File.FromLocalFile(name, prebuilt_path)
394
395 print "building image from target_files %s..." % (tree_subdir,)
396 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
397 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
398 os.path.join(unpack_dir, fs_config),
399 info_dict)
400 if data:
401 return File(name, data)
402 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800403
Doug Zongkereef39442009-04-02 12:14:19 -0700404
Doug Zongker75f17362009-12-08 13:46:44 -0800405def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800406 """Unzip the given archive into a temporary directory and return the name.
407
408 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
409 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
410
411 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
412 main file), open for reading.
413 """
Doug Zongkereef39442009-04-02 12:14:19 -0700414
415 tmp = tempfile.mkdtemp(prefix="targetfiles-")
416 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800417
418 def unzip_to_dir(filename, dirname):
419 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
420 if pattern is not None:
421 cmd.append(pattern)
422 p = Run(cmd, stdout=subprocess.PIPE)
423 p.communicate()
424 if p.returncode != 0:
425 raise ExternalError("failed to unzip input target-files \"%s\"" %
426 (filename,))
427
428 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
429 if m:
430 unzip_to_dir(m.group(1), tmp)
431 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
432 filename = m.group(1)
433 else:
434 unzip_to_dir(filename, tmp)
435
436 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700437
438
439def GetKeyPasswords(keylist):
440 """Given a list of keys, prompt the user to enter passwords for
441 those which require them. Return a {key: password} dict. password
442 will be None if the key has no password."""
443
Doug Zongker8ce7c252009-05-22 13:34:54 -0700444 no_passwords = []
445 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700446 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700447 devnull = open("/dev/null", "w+b")
448 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800449 # We don't need a password for things that aren't really keys.
450 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700451 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700452 continue
453
T.R. Fullhart37e10522013-03-18 10:31:26 -0700454 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700455 "-inform", "DER", "-nocrypt"],
456 stdin=devnull.fileno(),
457 stdout=devnull.fileno(),
458 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700459 p.communicate()
460 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700461 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700462 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700463 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700464 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
465 "-inform", "DER", "-passin", "pass:"],
466 stdin=devnull.fileno(),
467 stdout=devnull.fileno(),
468 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700469 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700470 if p.returncode == 0:
471 # Encrypted key with empty string as password.
472 key_passwords[k] = ''
473 elif stderr.startswith('Error decrypting key'):
474 # Definitely encrypted key.
475 # It would have said "Error reading key" if it didn't parse correctly.
476 need_passwords.append(k)
477 else:
478 # Potentially, a type of key that openssl doesn't understand.
479 # We'll let the routines in signapk.jar handle it.
480 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700481 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700482
T.R. Fullhart37e10522013-03-18 10:31:26 -0700483 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700484 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700485 return key_passwords
486
487
Doug Zongker951495f2009-08-14 12:44:19 -0700488def SignFile(input_name, output_name, key, password, align=None,
489 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700490 """Sign the input_name zip/jar/apk, producing output_name. Use the
491 given key and password (the latter may be None if the key does not
492 have a password.
493
494 If align is an integer > 1, zipalign is run to align stored files in
495 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700496
497 If whole_file is true, use the "-w" option to SignApk to embed a
498 signature that covers the whole file in the archive comment of the
499 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700500 """
Doug Zongker951495f2009-08-14 12:44:19 -0700501
Doug Zongkereef39442009-04-02 12:14:19 -0700502 if align == 0 or align == 1:
503 align = None
504
505 if align:
506 temp = tempfile.NamedTemporaryFile()
507 sign_name = temp.name
508 else:
509 sign_name = output_name
510
Baligh Uddin339ee492014-09-05 11:18:07 -0700511 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700512 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
513 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700514 if whole_file:
515 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700516 cmd.extend([key + OPTIONS.public_key_suffix,
517 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700518 input_name, sign_name])
519
520 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700521 if password is not None:
522 password += "\n"
523 p.communicate(password)
524 if p.returncode != 0:
525 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
526
527 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700528 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700529 p.communicate()
530 if p.returncode != 0:
531 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
532 temp.close()
533
534
Doug Zongker37974732010-09-16 17:44:38 -0700535def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700536 """Check the data string passed against the max size limit, if
537 any, for the given target. Raise exception if the data is too big.
538 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700539
Dan Albert8b72aef2015-03-23 19:13:21 -0700540 if target.endswith(".img"):
541 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700542 mount_point = "/" + target
543
Ying Wangf8824af2014-06-03 14:07:27 -0700544 fs_type = None
545 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700546 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700547 if mount_point == "/userdata":
548 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700549 p = info_dict["fstab"][mount_point]
550 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800551 device = p.device
552 if "/" in device:
553 device = device[device.rfind("/")+1:]
554 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700555 if not fs_type or not limit:
556 return
Doug Zongkereef39442009-04-02 12:14:19 -0700557
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700558 if fs_type == "yaffs2":
559 # image size should be increased by 1/64th to account for the
560 # spare area (64 bytes per 2k page)
561 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800562 size = len(data)
563 pct = float(size) * 100.0 / limit
564 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
565 if pct >= 99.0:
566 raise ExternalError(msg)
567 elif pct >= 95.0:
568 print
569 print " WARNING: ", msg
570 print
571 elif OPTIONS.verbose:
572 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700573
574
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800575def ReadApkCerts(tf_zip):
576 """Given a target_files ZipFile, parse the META/apkcerts.txt file
577 and return a {package: cert} dict."""
578 certmap = {}
579 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
580 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700581 if not line:
582 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800583 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
584 r'private_key="(.*)"$', line)
585 if m:
586 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700587 public_key_suffix_len = len(OPTIONS.public_key_suffix)
588 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800589 if cert in SPECIAL_CERT_STRINGS and not privkey:
590 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700591 elif (cert.endswith(OPTIONS.public_key_suffix) and
592 privkey.endswith(OPTIONS.private_key_suffix) and
593 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
594 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800595 else:
596 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
597 return certmap
598
599
Doug Zongkereef39442009-04-02 12:14:19 -0700600COMMON_DOCSTRING = """
601 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700602 Prepend <dir>/bin to the list of places to search for binaries
603 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700604
Doug Zongker05d3dea2009-06-22 11:32:31 -0700605 -s (--device_specific) <file>
606 Path to the python module containing device-specific
607 releasetools code.
608
Doug Zongker8bec09e2009-11-30 15:37:14 -0800609 -x (--extra) <key=value>
610 Add a key/value pair to the 'extras' dict, which device-specific
611 extension code may look at.
612
Doug Zongkereef39442009-04-02 12:14:19 -0700613 -v (--verbose)
614 Show command lines being executed.
615
616 -h (--help)
617 Display this usage message and exit.
618"""
619
620def Usage(docstring):
621 print docstring.rstrip("\n")
622 print COMMON_DOCSTRING
623
624
625def ParseOptions(argv,
626 docstring,
627 extra_opts="", extra_long_opts=(),
628 extra_option_handler=None):
629 """Parse the options in argv and return any arguments that aren't
630 flags. docstring is the calling module's docstring, to be displayed
631 for errors and -h. extra_opts and extra_long_opts are for flags
632 defined by the caller, which are processed by passing them to
633 extra_option_handler."""
634
635 try:
636 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800637 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700638 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700639 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddine2048682014-11-20 09:52:05 -0800640 "private_key_suffix=", "boot_signer_path=", "device_specific=",
641 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700642 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700643 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700644 Usage(docstring)
645 print "**", str(err), "**"
646 sys.exit(2)
647
Doug Zongkereef39442009-04-02 12:14:19 -0700648 for o, a in opts:
649 if o in ("-h", "--help"):
650 Usage(docstring)
651 sys.exit()
652 elif o in ("-v", "--verbose"):
653 OPTIONS.verbose = True
654 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700655 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700656 elif o in ("--signapk_path",):
657 OPTIONS.signapk_path = a
658 elif o in ("--extra_signapk_args",):
659 OPTIONS.extra_signapk_args = shlex.split(a)
660 elif o in ("--java_path",):
661 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700662 elif o in ("--java_args",):
663 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700664 elif o in ("--public_key_suffix",):
665 OPTIONS.public_key_suffix = a
666 elif o in ("--private_key_suffix",):
667 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800668 elif o in ("--boot_signer_path",):
669 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700670 elif o in ("-s", "--device_specific"):
671 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800672 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800673 key, value = a.split("=", 1)
674 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700675 else:
676 if extra_option_handler is None or not extra_option_handler(o, a):
677 assert False, "unknown option \"%s\"" % (o,)
678
Doug Zongker85448772014-09-09 14:59:20 -0700679 if OPTIONS.search_path:
680 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
681 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700682
683 return args
684
685
Doug Zongkerfc44a512014-08-26 13:10:25 -0700686def MakeTempFile(prefix=None, suffix=None):
687 """Make a temp file and add it to the list of things to be deleted
688 when Cleanup() is called. Return the filename."""
689 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
690 os.close(fd)
691 OPTIONS.tempfiles.append(fn)
692 return fn
693
694
Doug Zongkereef39442009-04-02 12:14:19 -0700695def Cleanup():
696 for i in OPTIONS.tempfiles:
697 if os.path.isdir(i):
698 shutil.rmtree(i)
699 else:
700 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700701
702
703class PasswordManager(object):
704 def __init__(self):
705 self.editor = os.getenv("EDITOR", None)
706 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
707
708 def GetPasswords(self, items):
709 """Get passwords corresponding to each string in 'items',
710 returning a dict. (The dict may have keys in addition to the
711 values in 'items'.)
712
713 Uses the passwords in $ANDROID_PW_FILE if available, letting the
714 user edit that file to add more needed passwords. If no editor is
715 available, or $ANDROID_PW_FILE isn't define, prompts the user
716 interactively in the ordinary way.
717 """
718
719 current = self.ReadFile()
720
721 first = True
722 while True:
723 missing = []
724 for i in items:
725 if i not in current or not current[i]:
726 missing.append(i)
727 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700728 if not missing:
729 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700730
731 for i in missing:
732 current[i] = ""
733
734 if not first:
735 print "key file %s still missing some passwords." % (self.pwfile,)
736 answer = raw_input("try to edit again? [y]> ").strip()
737 if answer and answer[0] not in 'yY':
738 raise RuntimeError("key passwords unavailable")
739 first = False
740
741 current = self.UpdateAndReadFile(current)
742
Dan Albert8b72aef2015-03-23 19:13:21 -0700743 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700744 """Prompt the user to enter a value (password) for each key in
745 'current' whose value is fales. Returns a new dict with all the
746 values.
747 """
748 result = {}
749 for k, v in sorted(current.iteritems()):
750 if v:
751 result[k] = v
752 else:
753 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700754 result[k] = getpass.getpass(
755 "Enter password for %s key> " % k).strip()
756 if result[k]:
757 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700758 return result
759
760 def UpdateAndReadFile(self, current):
761 if not self.editor or not self.pwfile:
762 return self.PromptResult(current)
763
764 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700765 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700766 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
767 f.write("# (Additional spaces are harmless.)\n\n")
768
769 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700770 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
771 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700772 f.write("[[[ %s ]]] %s\n" % (v, k))
773 if not v and first_line is None:
774 # position cursor on first line with no password.
775 first_line = i + 4
776 f.close()
777
778 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
779 _, _ = p.communicate()
780
781 return self.ReadFile()
782
783 def ReadFile(self):
784 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700785 if self.pwfile is None:
786 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700787 try:
788 f = open(self.pwfile, "r")
789 for line in f:
790 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700791 if not line or line[0] == '#':
792 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700793 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
794 if not m:
795 print "failed to parse password file: ", line
796 else:
797 result[m.group(2)] = m.group(1)
798 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700799 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700800 if e.errno != errno.ENOENT:
801 print "error reading password file: ", str(e)
802 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700803
804
Dan Albert8e0178d2015-01-27 15:53:15 -0800805def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
806 compress_type=None):
807 import datetime
808
809 # http://b/18015246
810 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
811 # for files larger than 2GiB. We can work around this by adjusting their
812 # limit. Note that `zipfile.writestr()` will not work for strings larger than
813 # 2GiB. The Python interpreter sometimes rejects strings that large (though
814 # it isn't clear to me exactly what circumstances cause this).
815 # `zipfile.write()` must be used directly to work around this.
816 #
817 # This mess can be avoided if we port to python3.
818 saved_zip64_limit = zipfile.ZIP64_LIMIT
819 zipfile.ZIP64_LIMIT = (1 << 32) - 1
820
821 if compress_type is None:
822 compress_type = zip_file.compression
823 if arcname is None:
824 arcname = filename
825
826 saved_stat = os.stat(filename)
827
828 try:
829 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
830 # file to be zipped and reset it when we're done.
831 os.chmod(filename, perms)
832
833 # Use a fixed timestamp so the output is repeatable.
834 epoch = datetime.datetime.fromtimestamp(0)
835 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
836 os.utime(filename, (timestamp, timestamp))
837
838 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
839 finally:
840 os.chmod(filename, saved_stat.st_mode)
841 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
842 zipfile.ZIP64_LIMIT = saved_zip64_limit
843
844
Dan Albert8b72aef2015-03-23 19:13:21 -0700845def ZipWriteStr(zip_file, filename, data, perms=0o644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700846 # use a fixed timestamp so the output is repeatable.
847 zinfo = zipfile.ZipInfo(filename=filename,
848 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800849 if compression is None:
Dan Albert8b72aef2015-03-23 19:13:21 -0700850 zinfo.compress_type = zip_file.compression
Geremy Condra36bd3652014-02-06 19:45:10 -0800851 else:
852 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700853 zinfo.external_attr = perms << 16
Dan Albert8b72aef2015-03-23 19:13:21 -0700854 zip_file.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700855
856
857class DeviceSpecificParams(object):
858 module = None
859 def __init__(self, **kwargs):
860 """Keyword arguments to the constructor become attributes of this
861 object, which is passed to all functions in the device-specific
862 module."""
863 for k, v in kwargs.iteritems():
864 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800865 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700866
867 if self.module is None:
868 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700869 if not path:
870 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700871 try:
872 if os.path.isdir(path):
873 info = imp.find_module("releasetools", [path])
874 else:
875 d, f = os.path.split(path)
876 b, x = os.path.splitext(f)
877 if x == ".py":
878 f = b
879 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800880 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700881 self.module = imp.load_module("device_specific", *info)
882 except ImportError:
883 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700884
885 def _DoCall(self, function_name, *args, **kwargs):
886 """Call the named function in the device-specific module, passing
887 the given args and kwargs. The first argument to the call will be
888 the DeviceSpecific object itself. If there is no module, or the
889 module does not define the function, return the value of the
890 'default' kwarg (which itself defaults to None)."""
891 if self.module is None or not hasattr(self.module, function_name):
892 return kwargs.get("default", None)
893 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
894
895 def FullOTA_Assertions(self):
896 """Called after emitting the block of assertions at the top of a
897 full OTA package. Implementations can add whatever additional
898 assertions they like."""
899 return self._DoCall("FullOTA_Assertions")
900
Doug Zongkere5ff5902012-01-17 10:55:37 -0800901 def FullOTA_InstallBegin(self):
902 """Called at the start of full OTA installation."""
903 return self._DoCall("FullOTA_InstallBegin")
904
Doug Zongker05d3dea2009-06-22 11:32:31 -0700905 def FullOTA_InstallEnd(self):
906 """Called at the end of full OTA installation; typically this is
907 used to install the image for the device's baseband processor."""
908 return self._DoCall("FullOTA_InstallEnd")
909
910 def IncrementalOTA_Assertions(self):
911 """Called after emitting the block of assertions at the top of an
912 incremental OTA package. Implementations can add whatever
913 additional assertions they like."""
914 return self._DoCall("IncrementalOTA_Assertions")
915
Doug Zongkere5ff5902012-01-17 10:55:37 -0800916 def IncrementalOTA_VerifyBegin(self):
917 """Called at the start of the verification phase of incremental
918 OTA installation; additional checks can be placed here to abort
919 the script before any changes are made."""
920 return self._DoCall("IncrementalOTA_VerifyBegin")
921
Doug Zongker05d3dea2009-06-22 11:32:31 -0700922 def IncrementalOTA_VerifyEnd(self):
923 """Called at the end of the verification phase of incremental OTA
924 installation; additional checks can be placed here to abort the
925 script before any changes are made."""
926 return self._DoCall("IncrementalOTA_VerifyEnd")
927
Doug Zongkere5ff5902012-01-17 10:55:37 -0800928 def IncrementalOTA_InstallBegin(self):
929 """Called at the start of incremental OTA installation (after
930 verification is complete)."""
931 return self._DoCall("IncrementalOTA_InstallBegin")
932
Doug Zongker05d3dea2009-06-22 11:32:31 -0700933 def IncrementalOTA_InstallEnd(self):
934 """Called at the end of incremental OTA installation; typically
935 this is used to install the image for the device's baseband
936 processor."""
937 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700938
939class File(object):
940 def __init__(self, name, data):
941 self.name = name
942 self.data = data
943 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800944 self.sha1 = sha1(data).hexdigest()
945
946 @classmethod
947 def FromLocalFile(cls, name, diskname):
948 f = open(diskname, "rb")
949 data = f.read()
950 f.close()
951 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700952
953 def WriteToTemp(self):
954 t = tempfile.NamedTemporaryFile()
955 t.write(self.data)
956 t.flush()
957 return t
958
Geremy Condra36bd3652014-02-06 19:45:10 -0800959 def AddToZip(self, z, compression=None):
960 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700961
962DIFF_PROGRAM_BY_EXT = {
963 ".gz" : "imgdiff",
964 ".zip" : ["imgdiff", "-z"],
965 ".jar" : ["imgdiff", "-z"],
966 ".apk" : ["imgdiff", "-z"],
967 ".img" : "imgdiff",
968 }
969
970class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700971 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700972 self.tf = tf
973 self.sf = sf
974 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700975 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700976
977 def ComputePatch(self):
978 """Compute the patch (as a string of data) needed to turn sf into
979 tf. Returns the same tuple as GetPatch()."""
980
981 tf = self.tf
982 sf = self.sf
983
Doug Zongker24cd2802012-08-14 16:36:15 -0700984 if self.diff_program:
985 diff_program = self.diff_program
986 else:
987 ext = os.path.splitext(tf.name)[1]
988 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700989
990 ttemp = tf.WriteToTemp()
991 stemp = sf.WriteToTemp()
992
993 ext = os.path.splitext(tf.name)[1]
994
995 try:
996 ptemp = tempfile.NamedTemporaryFile()
997 if isinstance(diff_program, list):
998 cmd = copy.copy(diff_program)
999 else:
1000 cmd = [diff_program]
1001 cmd.append(stemp.name)
1002 cmd.append(ttemp.name)
1003 cmd.append(ptemp.name)
1004 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001005 err = []
1006 def run():
1007 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 if e:
1009 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001010 th = threading.Thread(target=run)
1011 th.start()
1012 th.join(timeout=300) # 5 mins
1013 if th.is_alive():
1014 print "WARNING: diff command timed out"
1015 p.terminate()
1016 th.join(5)
1017 if th.is_alive():
1018 p.kill()
1019 th.join()
1020
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001021 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001022 print "WARNING: failure running %s:\n%s\n" % (
1023 diff_program, "".join(err))
1024 self.patch = None
1025 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001026 diff = ptemp.read()
1027 finally:
1028 ptemp.close()
1029 stemp.close()
1030 ttemp.close()
1031
1032 self.patch = diff
1033 return self.tf, self.sf, self.patch
1034
1035
1036 def GetPatch(self):
1037 """Return a tuple (target_file, source_file, patch_data).
1038 patch_data may be None if ComputePatch hasn't been called, or if
1039 computing the patch failed."""
1040 return self.tf, self.sf, self.patch
1041
1042
1043def ComputeDifferences(diffs):
1044 """Call ComputePatch on all the Difference objects in 'diffs'."""
1045 print len(diffs), "diffs to compute"
1046
1047 # Do the largest files first, to try and reduce the long-pole effect.
1048 by_size = [(i.tf.size, i) for i in diffs]
1049 by_size.sort(reverse=True)
1050 by_size = [i[1] for i in by_size]
1051
1052 lock = threading.Lock()
1053 diff_iter = iter(by_size) # accessed under lock
1054
1055 def worker():
1056 try:
1057 lock.acquire()
1058 for d in diff_iter:
1059 lock.release()
1060 start = time.time()
1061 d.ComputePatch()
1062 dur = time.time() - start
1063 lock.acquire()
1064
1065 tf, sf, patch = d.GetPatch()
1066 if sf.name == tf.name:
1067 name = tf.name
1068 else:
1069 name = "%s (%s)" % (tf.name, sf.name)
1070 if patch is None:
1071 print "patching failed! %s" % (name,)
1072 else:
1073 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1074 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1075 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001076 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001077 print e
1078 raise
1079
1080 # start worker threads; wait for them all to finish.
1081 threads = [threading.Thread(target=worker)
1082 for i in range(OPTIONS.worker_threads)]
1083 for th in threads:
1084 th.start()
1085 while threads:
1086 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001087
1088
Dan Albert8b72aef2015-03-23 19:13:21 -07001089class BlockDifference(object):
1090 def __init__(self, partition, tgt, src=None, check_first_block=False,
1091 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001092 self.tgt = tgt
1093 self.src = src
1094 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001095 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001096
Tao Baodd2a5892015-03-12 12:32:37 -07001097 if version is None:
1098 version = 1
1099 if OPTIONS.info_dict:
1100 version = max(
1101 int(i) for i in
1102 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1103 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001104
1105 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001106 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001107 tmpdir = tempfile.mkdtemp()
1108 OPTIONS.tempfiles.append(tmpdir)
1109 self.path = os.path.join(tmpdir, partition)
1110 b.Compute(self.path)
1111
1112 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1113
1114 def WriteScript(self, script, output_zip, progress=None):
1115 if not self.src:
1116 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001117 script.Print("Patching %s image unconditionally..." % (self.partition,))
1118 else:
1119 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001120
Dan Albert8b72aef2015-03-23 19:13:21 -07001121 if progress:
1122 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001123 self._WriteUpdate(script, output_zip)
1124
1125 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001126 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001127 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001128 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001129 else:
Michael Runge910b0052015-02-11 19:28:08 -08001130 if self.version >= 3:
1131 script.AppendExtra(('if block_image_verify("%s", '
1132 'package_extract_file("%s.transfer.list"), '
1133 '"%s.new.dat", "%s.patch.dat") then') %
1134 (self.device, partition, partition, partition))
1135 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001136 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1137 self.device, self.src.care_map.to_string_raw(),
1138 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001139 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001140 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001141
Tao Baodd2a5892015-03-12 12:32:37 -07001142 # When generating incrementals for the system and vendor partitions,
1143 # explicitly check the first block (which contains the superblock) of
1144 # the partition to see if it's what we expect. If this check fails,
1145 # give an explicit log message about the partition having been
1146 # remounted R/W (the most likely explanation) and the need to flash to
1147 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001148 if self.check_first_block:
1149 self._CheckFirstBlock(script)
1150
Tao Baodd2a5892015-03-12 12:32:37 -07001151 # Abort the OTA update. Note that the incremental OTA cannot be applied
1152 # even if it may match the checksum of the target partition.
1153 # a) If version < 3, operations like move and erase will make changes
1154 # unconditionally and damage the partition.
1155 # b) If version >= 3, it won't even reach here.
1156 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1157 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001158
1159 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001160 ZipWrite(output_zip,
1161 '{}.transfer.list'.format(self.path),
1162 '{}.transfer.list'.format(self.partition))
1163 ZipWrite(output_zip,
1164 '{}.new.dat'.format(self.path),
1165 '{}.new.dat'.format(self.partition))
1166 ZipWrite(output_zip,
1167 '{}.patch.dat'.format(self.path),
1168 '{}.patch.dat'.format(self.partition),
1169 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001170
Dan Albert8e0178d2015-01-27 15:53:15 -08001171 call = ('block_image_update("{device}", '
1172 'package_extract_file("{partition}.transfer.list"), '
1173 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1174 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001176
Dan Albert8b72aef2015-03-23 19:13:21 -07001177 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001178 data = source.ReadRangeSet(ranges)
1179 ctx = sha1()
1180
1181 for p in data:
1182 ctx.update(p)
1183
1184 return ctx.hexdigest()
1185
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001186 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001187 r = rangelib.RangeSet((0, 1))
1188 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001189
1190 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1191 'abort("%s has been remounted R/W; '
1192 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001193 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001194 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001195
1196DataImage = blockimgdiff.DataImage
1197
1198
Doug Zongker96a57e72010-09-26 14:57:41 -07001199# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001200PARTITION_TYPES = {
1201 "yaffs2": "MTD",
1202 "mtd": "MTD",
1203 "ext4": "EMMC",
1204 "emmc": "EMMC",
1205 "f2fs": "EMMC"
1206}
Doug Zongker96a57e72010-09-26 14:57:41 -07001207
1208def GetTypeAndDevice(mount_point, info):
1209 fstab = info["fstab"]
1210 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001211 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1212 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001213 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001214 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001215
1216
1217def ParseCertificate(data):
1218 """Parse a PEM-format certificate."""
1219 cert = []
1220 save = False
1221 for line in data.split("\n"):
1222 if "--END CERTIFICATE--" in line:
1223 break
1224 if save:
1225 cert.append(line)
1226 if "--BEGIN CERTIFICATE--" in line:
1227 save = True
1228 cert = "".join(cert).decode('base64')
1229 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001230
Doug Zongker412c02f2014-02-13 10:58:24 -08001231def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1232 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001233 """Generate a binary patch that creates the recovery image starting
1234 with the boot image. (Most of the space in these images is just the
1235 kernel, which is identical for the two, so the resulting patch
1236 should be efficient.) Add it to the output zip, along with a shell
1237 script that is run from init.rc on first boot to actually do the
1238 patching and install the new recovery image.
1239
1240 recovery_img and boot_img should be File objects for the
1241 corresponding images. info should be the dictionary returned by
1242 common.LoadInfoDict() on the input target_files.
1243 """
1244
Doug Zongker412c02f2014-02-13 10:58:24 -08001245 if info_dict is None:
1246 info_dict = OPTIONS.info_dict
1247
Doug Zongkerc9253822014-02-04 12:17:58 -08001248 diff_program = ["imgdiff"]
1249 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1250 if os.path.exists(path):
1251 diff_program.append("-b")
1252 diff_program.append(path)
1253 bonus_args = "-b /system/etc/recovery-resource.dat"
1254 else:
1255 bonus_args = ""
1256
1257 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1258 _, _, patch = d.ComputePatch()
1259 output_sink("recovery-from-boot.p", patch)
1260
Ying Wanga961a092014-07-29 11:42:37 -07001261 td_pair = GetTypeAndDevice("/boot", info_dict)
1262 if not td_pair:
1263 return
1264 boot_type, boot_device = td_pair
1265 td_pair = GetTypeAndDevice("/recovery", info_dict)
1266 if not td_pair:
1267 return
1268 recovery_type, recovery_device = td_pair
Doug Zongkerc9253822014-02-04 12:17:58 -08001269
1270 sh = """#!/system/bin/sh
1271if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1272 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"
1273else
1274 log -t recovery "Recovery image already installed"
1275fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001276""" % {'boot_size': boot_img.size,
1277 'boot_sha1': boot_img.sha1,
1278 'recovery_size': recovery_img.size,
1279 'recovery_sha1': recovery_img.sha1,
1280 'boot_type': boot_type,
1281 'boot_device': boot_device,
1282 'recovery_type': recovery_type,
1283 'recovery_device': recovery_device,
1284 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001285
1286 # The install script location moved from /system/etc to /system/bin
1287 # in the L release. Parse the init.rc file to find out where the
1288 # target-files expects it to be, and put it there.
1289 sh_location = "etc/install-recovery.sh"
1290 try:
1291 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1292 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001293 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001294 if m:
1295 sh_location = m.group(1)
1296 print "putting script in", sh_location
1297 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001298 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001299 print "failed to read init.rc: %s" % (e,)
1300
1301 output_sink(sh_location, sh)