blob: 59e81c15029171325e66fa9920564f434b002ca9 [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
Tao Baod95e9fd2015-03-29 23:07:41 -0700351 img_unsigned = None
352 if info_dict.get("vboot", None):
353 img_unsigned = tempfile.NamedTemporaryFile()
354 cmd.extend(["--ramdisk", ramdisk_img.name,
355 "--output", img_unsigned.name])
356 else:
357 cmd.extend(["--ramdisk", ramdisk_img.name,
358 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700359
360 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700361 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700362 assert p.returncode == 0, "mkbootimg of %s image failed" % (
363 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700365 if info_dict.get("verity_key", None):
366 path = "/" + os.path.basename(sourcedir).lower()
Dan Albertcd9ecc02015-03-27 16:37:23 -0700367 cmd = [OPTIONS.boot_signer_path, path, img.name,
368 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700369 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700370 p = Run(cmd, stdout=subprocess.PIPE)
371 p.communicate()
372 assert p.returncode == 0, "boot_signer of %s image failed" % path
373
Tao Baod95e9fd2015-03-29 23:07:41 -0700374 # Sign the image if vboot is non-empty.
375 elif info_dict.get("vboot", None):
376 path = "/" + os.path.basename(sourcedir).lower()
377 img_keyblock = tempfile.NamedTemporaryFile()
378 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
379 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
380 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
381 img.name]
382 p = Run(cmd, stdout=subprocess.PIPE)
383 p.communicate()
384 assert p.returncode == 0, "vboot_signer of %s image failed" % path
385
Doug Zongkereef39442009-04-02 12:14:19 -0700386 img.seek(os.SEEK_SET, 0)
387 data = img.read()
388
389 ramdisk_img.close()
390 img.close()
391
392 return data
393
394
Doug Zongkerd5131602012-08-02 14:46:42 -0700395def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
396 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800397 """Return a File object (with name 'name') with the desired bootable
398 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700399 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
400 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800401 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700402
Doug Zongker55d93282011-01-25 17:03:34 -0800403 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
404 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700405 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800406 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700407
408 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
409 if os.path.exists(prebuilt_path):
410 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
411 return File.FromLocalFile(name, prebuilt_path)
412
413 print "building image from target_files %s..." % (tree_subdir,)
414 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
415 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
416 os.path.join(unpack_dir, fs_config),
417 info_dict)
418 if data:
419 return File(name, data)
420 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800421
Doug Zongkereef39442009-04-02 12:14:19 -0700422
Doug Zongker75f17362009-12-08 13:46:44 -0800423def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800424 """Unzip the given archive into a temporary directory and return the name.
425
426 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
427 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
428
429 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
430 main file), open for reading.
431 """
Doug Zongkereef39442009-04-02 12:14:19 -0700432
433 tmp = tempfile.mkdtemp(prefix="targetfiles-")
434 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800435
436 def unzip_to_dir(filename, dirname):
437 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
438 if pattern is not None:
439 cmd.append(pattern)
440 p = Run(cmd, stdout=subprocess.PIPE)
441 p.communicate()
442 if p.returncode != 0:
443 raise ExternalError("failed to unzip input target-files \"%s\"" %
444 (filename,))
445
446 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
447 if m:
448 unzip_to_dir(m.group(1), tmp)
449 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
450 filename = m.group(1)
451 else:
452 unzip_to_dir(filename, tmp)
453
454 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700455
456
457def GetKeyPasswords(keylist):
458 """Given a list of keys, prompt the user to enter passwords for
459 those which require them. Return a {key: password} dict. password
460 will be None if the key has no password."""
461
Doug Zongker8ce7c252009-05-22 13:34:54 -0700462 no_passwords = []
463 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700464 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700465 devnull = open("/dev/null", "w+b")
466 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800467 # We don't need a password for things that aren't really keys.
468 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700469 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700470 continue
471
T.R. Fullhart37e10522013-03-18 10:31:26 -0700472 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700473 "-inform", "DER", "-nocrypt"],
474 stdin=devnull.fileno(),
475 stdout=devnull.fileno(),
476 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700477 p.communicate()
478 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700479 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700480 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700481 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700482 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
483 "-inform", "DER", "-passin", "pass:"],
484 stdin=devnull.fileno(),
485 stdout=devnull.fileno(),
486 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700487 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700488 if p.returncode == 0:
489 # Encrypted key with empty string as password.
490 key_passwords[k] = ''
491 elif stderr.startswith('Error decrypting key'):
492 # Definitely encrypted key.
493 # It would have said "Error reading key" if it didn't parse correctly.
494 need_passwords.append(k)
495 else:
496 # Potentially, a type of key that openssl doesn't understand.
497 # We'll let the routines in signapk.jar handle it.
498 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700499 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700500
T.R. Fullhart37e10522013-03-18 10:31:26 -0700501 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700502 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700503 return key_passwords
504
505
Doug Zongker951495f2009-08-14 12:44:19 -0700506def SignFile(input_name, output_name, key, password, align=None,
507 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700508 """Sign the input_name zip/jar/apk, producing output_name. Use the
509 given key and password (the latter may be None if the key does not
510 have a password.
511
512 If align is an integer > 1, zipalign is run to align stored files in
513 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700514
515 If whole_file is true, use the "-w" option to SignApk to embed a
516 signature that covers the whole file in the archive comment of the
517 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700518 """
Doug Zongker951495f2009-08-14 12:44:19 -0700519
Doug Zongkereef39442009-04-02 12:14:19 -0700520 if align == 0 or align == 1:
521 align = None
522
523 if align:
524 temp = tempfile.NamedTemporaryFile()
525 sign_name = temp.name
526 else:
527 sign_name = output_name
528
Baligh Uddin339ee492014-09-05 11:18:07 -0700529 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700530 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
531 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700532 if whole_file:
533 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700534 cmd.extend([key + OPTIONS.public_key_suffix,
535 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700536 input_name, sign_name])
537
538 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700539 if password is not None:
540 password += "\n"
541 p.communicate(password)
542 if p.returncode != 0:
543 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
544
545 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700546 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700547 p.communicate()
548 if p.returncode != 0:
549 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
550 temp.close()
551
552
Doug Zongker37974732010-09-16 17:44:38 -0700553def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700554 """Check the data string passed against the max size limit, if
555 any, for the given target. Raise exception if the data is too big.
556 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700557
Dan Albert8b72aef2015-03-23 19:13:21 -0700558 if target.endswith(".img"):
559 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700560 mount_point = "/" + target
561
Ying Wangf8824af2014-06-03 14:07:27 -0700562 fs_type = None
563 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700564 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700565 if mount_point == "/userdata":
566 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700567 p = info_dict["fstab"][mount_point]
568 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800569 device = p.device
570 if "/" in device:
571 device = device[device.rfind("/")+1:]
572 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700573 if not fs_type or not limit:
574 return
Doug Zongkereef39442009-04-02 12:14:19 -0700575
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700576 if fs_type == "yaffs2":
577 # image size should be increased by 1/64th to account for the
578 # spare area (64 bytes per 2k page)
579 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800580 size = len(data)
581 pct = float(size) * 100.0 / limit
582 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
583 if pct >= 99.0:
584 raise ExternalError(msg)
585 elif pct >= 95.0:
586 print
587 print " WARNING: ", msg
588 print
589 elif OPTIONS.verbose:
590 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700591
592
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800593def ReadApkCerts(tf_zip):
594 """Given a target_files ZipFile, parse the META/apkcerts.txt file
595 and return a {package: cert} dict."""
596 certmap = {}
597 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
598 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700599 if not line:
600 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800601 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
602 r'private_key="(.*)"$', line)
603 if m:
604 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 public_key_suffix_len = len(OPTIONS.public_key_suffix)
606 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800607 if cert in SPECIAL_CERT_STRINGS and not privkey:
608 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700609 elif (cert.endswith(OPTIONS.public_key_suffix) and
610 privkey.endswith(OPTIONS.private_key_suffix) and
611 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
612 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800613 else:
614 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
615 return certmap
616
617
Doug Zongkereef39442009-04-02 12:14:19 -0700618COMMON_DOCSTRING = """
619 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700620 Prepend <dir>/bin to the list of places to search for binaries
621 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700622
Doug Zongker05d3dea2009-06-22 11:32:31 -0700623 -s (--device_specific) <file>
624 Path to the python module containing device-specific
625 releasetools code.
626
Doug Zongker8bec09e2009-11-30 15:37:14 -0800627 -x (--extra) <key=value>
628 Add a key/value pair to the 'extras' dict, which device-specific
629 extension code may look at.
630
Doug Zongkereef39442009-04-02 12:14:19 -0700631 -v (--verbose)
632 Show command lines being executed.
633
634 -h (--help)
635 Display this usage message and exit.
636"""
637
638def Usage(docstring):
639 print docstring.rstrip("\n")
640 print COMMON_DOCSTRING
641
642
643def ParseOptions(argv,
644 docstring,
645 extra_opts="", extra_long_opts=(),
646 extra_option_handler=None):
647 """Parse the options in argv and return any arguments that aren't
648 flags. docstring is the calling module's docstring, to be displayed
649 for errors and -h. extra_opts and extra_long_opts are for flags
650 defined by the caller, which are processed by passing them to
651 extra_option_handler."""
652
653 try:
654 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800655 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700656 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700657 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddine2048682014-11-20 09:52:05 -0800658 "private_key_suffix=", "boot_signer_path=", "device_specific=",
659 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700660 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700661 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700662 Usage(docstring)
663 print "**", str(err), "**"
664 sys.exit(2)
665
Doug Zongkereef39442009-04-02 12:14:19 -0700666 for o, a in opts:
667 if o in ("-h", "--help"):
668 Usage(docstring)
669 sys.exit()
670 elif o in ("-v", "--verbose"):
671 OPTIONS.verbose = True
672 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700673 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700674 elif o in ("--signapk_path",):
675 OPTIONS.signapk_path = a
676 elif o in ("--extra_signapk_args",):
677 OPTIONS.extra_signapk_args = shlex.split(a)
678 elif o in ("--java_path",):
679 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700680 elif o in ("--java_args",):
681 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700682 elif o in ("--public_key_suffix",):
683 OPTIONS.public_key_suffix = a
684 elif o in ("--private_key_suffix",):
685 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800686 elif o in ("--boot_signer_path",):
687 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700688 elif o in ("-s", "--device_specific"):
689 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800690 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800691 key, value = a.split("=", 1)
692 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700693 else:
694 if extra_option_handler is None or not extra_option_handler(o, a):
695 assert False, "unknown option \"%s\"" % (o,)
696
Doug Zongker85448772014-09-09 14:59:20 -0700697 if OPTIONS.search_path:
698 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
699 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700700
701 return args
702
703
Doug Zongkerfc44a512014-08-26 13:10:25 -0700704def MakeTempFile(prefix=None, suffix=None):
705 """Make a temp file and add it to the list of things to be deleted
706 when Cleanup() is called. Return the filename."""
707 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
708 os.close(fd)
709 OPTIONS.tempfiles.append(fn)
710 return fn
711
712
Doug Zongkereef39442009-04-02 12:14:19 -0700713def Cleanup():
714 for i in OPTIONS.tempfiles:
715 if os.path.isdir(i):
716 shutil.rmtree(i)
717 else:
718 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700719
720
721class PasswordManager(object):
722 def __init__(self):
723 self.editor = os.getenv("EDITOR", None)
724 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
725
726 def GetPasswords(self, items):
727 """Get passwords corresponding to each string in 'items',
728 returning a dict. (The dict may have keys in addition to the
729 values in 'items'.)
730
731 Uses the passwords in $ANDROID_PW_FILE if available, letting the
732 user edit that file to add more needed passwords. If no editor is
733 available, or $ANDROID_PW_FILE isn't define, prompts the user
734 interactively in the ordinary way.
735 """
736
737 current = self.ReadFile()
738
739 first = True
740 while True:
741 missing = []
742 for i in items:
743 if i not in current or not current[i]:
744 missing.append(i)
745 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700746 if not missing:
747 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700748
749 for i in missing:
750 current[i] = ""
751
752 if not first:
753 print "key file %s still missing some passwords." % (self.pwfile,)
754 answer = raw_input("try to edit again? [y]> ").strip()
755 if answer and answer[0] not in 'yY':
756 raise RuntimeError("key passwords unavailable")
757 first = False
758
759 current = self.UpdateAndReadFile(current)
760
Dan Albert8b72aef2015-03-23 19:13:21 -0700761 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700762 """Prompt the user to enter a value (password) for each key in
763 'current' whose value is fales. Returns a new dict with all the
764 values.
765 """
766 result = {}
767 for k, v in sorted(current.iteritems()):
768 if v:
769 result[k] = v
770 else:
771 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 result[k] = getpass.getpass(
773 "Enter password for %s key> " % k).strip()
774 if result[k]:
775 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776 return result
777
778 def UpdateAndReadFile(self, current):
779 if not self.editor or not self.pwfile:
780 return self.PromptResult(current)
781
782 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700783 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700784 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
785 f.write("# (Additional spaces are harmless.)\n\n")
786
787 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700788 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
789 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790 f.write("[[[ %s ]]] %s\n" % (v, k))
791 if not v and first_line is None:
792 # position cursor on first line with no password.
793 first_line = i + 4
794 f.close()
795
796 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
797 _, _ = p.communicate()
798
799 return self.ReadFile()
800
801 def ReadFile(self):
802 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700803 if self.pwfile is None:
804 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700805 try:
806 f = open(self.pwfile, "r")
807 for line in f:
808 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700809 if not line or line[0] == '#':
810 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700811 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
812 if not m:
813 print "failed to parse password file: ", line
814 else:
815 result[m.group(2)] = m.group(1)
816 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700817 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700818 if e.errno != errno.ENOENT:
819 print "error reading password file: ", str(e)
820 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700821
822
Dan Albert8e0178d2015-01-27 15:53:15 -0800823def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
824 compress_type=None):
825 import datetime
826
827 # http://b/18015246
828 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
829 # for files larger than 2GiB. We can work around this by adjusting their
830 # limit. Note that `zipfile.writestr()` will not work for strings larger than
831 # 2GiB. The Python interpreter sometimes rejects strings that large (though
832 # it isn't clear to me exactly what circumstances cause this).
833 # `zipfile.write()` must be used directly to work around this.
834 #
835 # This mess can be avoided if we port to python3.
836 saved_zip64_limit = zipfile.ZIP64_LIMIT
837 zipfile.ZIP64_LIMIT = (1 << 32) - 1
838
839 if compress_type is None:
840 compress_type = zip_file.compression
841 if arcname is None:
842 arcname = filename
843
844 saved_stat = os.stat(filename)
845
846 try:
847 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
848 # file to be zipped and reset it when we're done.
849 os.chmod(filename, perms)
850
851 # Use a fixed timestamp so the output is repeatable.
852 epoch = datetime.datetime.fromtimestamp(0)
853 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
854 os.utime(filename, (timestamp, timestamp))
855
856 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
857 finally:
858 os.chmod(filename, saved_stat.st_mode)
859 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
860 zipfile.ZIP64_LIMIT = saved_zip64_limit
861
862
Dan Albert8b72aef2015-03-23 19:13:21 -0700863def ZipWriteStr(zip_file, filename, data, perms=0o644, compression=None):
Doug Zongker048e7ca2009-06-15 14:31:53 -0700864 # use a fixed timestamp so the output is repeatable.
865 zinfo = zipfile.ZipInfo(filename=filename,
866 date_time=(2009, 1, 1, 0, 0, 0))
Geremy Condra36bd3652014-02-06 19:45:10 -0800867 if compression is None:
Dan Albert8b72aef2015-03-23 19:13:21 -0700868 zinfo.compress_type = zip_file.compression
Geremy Condra36bd3652014-02-06 19:45:10 -0800869 else:
870 zinfo.compress_type = compression
Doug Zongker048e7ca2009-06-15 14:31:53 -0700871 zinfo.external_attr = perms << 16
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 zip_file.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700873
874
875class DeviceSpecificParams(object):
876 module = None
877 def __init__(self, **kwargs):
878 """Keyword arguments to the constructor become attributes of this
879 object, which is passed to all functions in the device-specific
880 module."""
881 for k, v in kwargs.iteritems():
882 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800883 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700884
885 if self.module is None:
886 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700887 if not path:
888 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700889 try:
890 if os.path.isdir(path):
891 info = imp.find_module("releasetools", [path])
892 else:
893 d, f = os.path.split(path)
894 b, x = os.path.splitext(f)
895 if x == ".py":
896 f = b
897 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800898 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700899 self.module = imp.load_module("device_specific", *info)
900 except ImportError:
901 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700902
903 def _DoCall(self, function_name, *args, **kwargs):
904 """Call the named function in the device-specific module, passing
905 the given args and kwargs. The first argument to the call will be
906 the DeviceSpecific object itself. If there is no module, or the
907 module does not define the function, return the value of the
908 'default' kwarg (which itself defaults to None)."""
909 if self.module is None or not hasattr(self.module, function_name):
910 return kwargs.get("default", None)
911 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
912
913 def FullOTA_Assertions(self):
914 """Called after emitting the block of assertions at the top of a
915 full OTA package. Implementations can add whatever additional
916 assertions they like."""
917 return self._DoCall("FullOTA_Assertions")
918
Doug Zongkere5ff5902012-01-17 10:55:37 -0800919 def FullOTA_InstallBegin(self):
920 """Called at the start of full OTA installation."""
921 return self._DoCall("FullOTA_InstallBegin")
922
Doug Zongker05d3dea2009-06-22 11:32:31 -0700923 def FullOTA_InstallEnd(self):
924 """Called at the end of full OTA installation; typically this is
925 used to install the image for the device's baseband processor."""
926 return self._DoCall("FullOTA_InstallEnd")
927
928 def IncrementalOTA_Assertions(self):
929 """Called after emitting the block of assertions at the top of an
930 incremental OTA package. Implementations can add whatever
931 additional assertions they like."""
932 return self._DoCall("IncrementalOTA_Assertions")
933
Doug Zongkere5ff5902012-01-17 10:55:37 -0800934 def IncrementalOTA_VerifyBegin(self):
935 """Called at the start of the verification phase of incremental
936 OTA installation; additional checks can be placed here to abort
937 the script before any changes are made."""
938 return self._DoCall("IncrementalOTA_VerifyBegin")
939
Doug Zongker05d3dea2009-06-22 11:32:31 -0700940 def IncrementalOTA_VerifyEnd(self):
941 """Called at the end of the verification phase of incremental OTA
942 installation; additional checks can be placed here to abort the
943 script before any changes are made."""
944 return self._DoCall("IncrementalOTA_VerifyEnd")
945
Doug Zongkere5ff5902012-01-17 10:55:37 -0800946 def IncrementalOTA_InstallBegin(self):
947 """Called at the start of incremental OTA installation (after
948 verification is complete)."""
949 return self._DoCall("IncrementalOTA_InstallBegin")
950
Doug Zongker05d3dea2009-06-22 11:32:31 -0700951 def IncrementalOTA_InstallEnd(self):
952 """Called at the end of incremental OTA installation; typically
953 this is used to install the image for the device's baseband
954 processor."""
955 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700956
957class File(object):
958 def __init__(self, name, data):
959 self.name = name
960 self.data = data
961 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800962 self.sha1 = sha1(data).hexdigest()
963
964 @classmethod
965 def FromLocalFile(cls, name, diskname):
966 f = open(diskname, "rb")
967 data = f.read()
968 f.close()
969 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700970
971 def WriteToTemp(self):
972 t = tempfile.NamedTemporaryFile()
973 t.write(self.data)
974 t.flush()
975 return t
976
Geremy Condra36bd3652014-02-06 19:45:10 -0800977 def AddToZip(self, z, compression=None):
978 ZipWriteStr(z, self.name, self.data, compression=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700979
980DIFF_PROGRAM_BY_EXT = {
981 ".gz" : "imgdiff",
982 ".zip" : ["imgdiff", "-z"],
983 ".jar" : ["imgdiff", "-z"],
984 ".apk" : ["imgdiff", "-z"],
985 ".img" : "imgdiff",
986 }
987
988class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -0700989 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700990 self.tf = tf
991 self.sf = sf
992 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -0700993 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700994
995 def ComputePatch(self):
996 """Compute the patch (as a string of data) needed to turn sf into
997 tf. Returns the same tuple as GetPatch()."""
998
999 tf = self.tf
1000 sf = self.sf
1001
Doug Zongker24cd2802012-08-14 16:36:15 -07001002 if self.diff_program:
1003 diff_program = self.diff_program
1004 else:
1005 ext = os.path.splitext(tf.name)[1]
1006 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001007
1008 ttemp = tf.WriteToTemp()
1009 stemp = sf.WriteToTemp()
1010
1011 ext = os.path.splitext(tf.name)[1]
1012
1013 try:
1014 ptemp = tempfile.NamedTemporaryFile()
1015 if isinstance(diff_program, list):
1016 cmd = copy.copy(diff_program)
1017 else:
1018 cmd = [diff_program]
1019 cmd.append(stemp.name)
1020 cmd.append(ttemp.name)
1021 cmd.append(ptemp.name)
1022 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001023 err = []
1024 def run():
1025 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001026 if e:
1027 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001028 th = threading.Thread(target=run)
1029 th.start()
1030 th.join(timeout=300) # 5 mins
1031 if th.is_alive():
1032 print "WARNING: diff command timed out"
1033 p.terminate()
1034 th.join(5)
1035 if th.is_alive():
1036 p.kill()
1037 th.join()
1038
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001039 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001040 print "WARNING: failure running %s:\n%s\n" % (
1041 diff_program, "".join(err))
1042 self.patch = None
1043 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001044 diff = ptemp.read()
1045 finally:
1046 ptemp.close()
1047 stemp.close()
1048 ttemp.close()
1049
1050 self.patch = diff
1051 return self.tf, self.sf, self.patch
1052
1053
1054 def GetPatch(self):
1055 """Return a tuple (target_file, source_file, patch_data).
1056 patch_data may be None if ComputePatch hasn't been called, or if
1057 computing the patch failed."""
1058 return self.tf, self.sf, self.patch
1059
1060
1061def ComputeDifferences(diffs):
1062 """Call ComputePatch on all the Difference objects in 'diffs'."""
1063 print len(diffs), "diffs to compute"
1064
1065 # Do the largest files first, to try and reduce the long-pole effect.
1066 by_size = [(i.tf.size, i) for i in diffs]
1067 by_size.sort(reverse=True)
1068 by_size = [i[1] for i in by_size]
1069
1070 lock = threading.Lock()
1071 diff_iter = iter(by_size) # accessed under lock
1072
1073 def worker():
1074 try:
1075 lock.acquire()
1076 for d in diff_iter:
1077 lock.release()
1078 start = time.time()
1079 d.ComputePatch()
1080 dur = time.time() - start
1081 lock.acquire()
1082
1083 tf, sf, patch = d.GetPatch()
1084 if sf.name == tf.name:
1085 name = tf.name
1086 else:
1087 name = "%s (%s)" % (tf.name, sf.name)
1088 if patch is None:
1089 print "patching failed! %s" % (name,)
1090 else:
1091 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1092 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1093 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001094 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001095 print e
1096 raise
1097
1098 # start worker threads; wait for them all to finish.
1099 threads = [threading.Thread(target=worker)
1100 for i in range(OPTIONS.worker_threads)]
1101 for th in threads:
1102 th.start()
1103 while threads:
1104 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001105
1106
Dan Albert8b72aef2015-03-23 19:13:21 -07001107class BlockDifference(object):
1108 def __init__(self, partition, tgt, src=None, check_first_block=False,
1109 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001110 self.tgt = tgt
1111 self.src = src
1112 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001113 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001114
Tao Baodd2a5892015-03-12 12:32:37 -07001115 if version is None:
1116 version = 1
1117 if OPTIONS.info_dict:
1118 version = max(
1119 int(i) for i in
1120 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1121 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001122
1123 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001124 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001125 tmpdir = tempfile.mkdtemp()
1126 OPTIONS.tempfiles.append(tmpdir)
1127 self.path = os.path.join(tmpdir, partition)
1128 b.Compute(self.path)
1129
1130 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1131
1132 def WriteScript(self, script, output_zip, progress=None):
1133 if not self.src:
1134 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001135 script.Print("Patching %s image unconditionally..." % (self.partition,))
1136 else:
1137 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001138
Dan Albert8b72aef2015-03-23 19:13:21 -07001139 if progress:
1140 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001141 self._WriteUpdate(script, output_zip)
1142
1143 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001144 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001145 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001146 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001147 else:
Michael Runge910b0052015-02-11 19:28:08 -08001148 if self.version >= 3:
1149 script.AppendExtra(('if block_image_verify("%s", '
1150 'package_extract_file("%s.transfer.list"), '
1151 '"%s.new.dat", "%s.patch.dat") then') %
1152 (self.device, partition, partition, partition))
1153 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001154 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1155 self.device, self.src.care_map.to_string_raw(),
1156 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001157 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001158 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001159
Tao Baodd2a5892015-03-12 12:32:37 -07001160 # When generating incrementals for the system and vendor partitions,
1161 # explicitly check the first block (which contains the superblock) of
1162 # the partition to see if it's what we expect. If this check fails,
1163 # give an explicit log message about the partition having been
1164 # remounted R/W (the most likely explanation) and the need to flash to
1165 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001166 if self.check_first_block:
1167 self._CheckFirstBlock(script)
1168
Tao Baodd2a5892015-03-12 12:32:37 -07001169 # Abort the OTA update. Note that the incremental OTA cannot be applied
1170 # even if it may match the checksum of the target partition.
1171 # a) If version < 3, operations like move and erase will make changes
1172 # unconditionally and damage the partition.
1173 # b) If version >= 3, it won't even reach here.
1174 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1175 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001176
1177 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001178 ZipWrite(output_zip,
1179 '{}.transfer.list'.format(self.path),
1180 '{}.transfer.list'.format(self.partition))
1181 ZipWrite(output_zip,
1182 '{}.new.dat'.format(self.path),
1183 '{}.new.dat'.format(self.partition))
1184 ZipWrite(output_zip,
1185 '{}.patch.dat'.format(self.path),
1186 '{}.patch.dat'.format(self.partition),
1187 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001188
Dan Albert8e0178d2015-01-27 15:53:15 -08001189 call = ('block_image_update("{device}", '
1190 'package_extract_file("{partition}.transfer.list"), '
1191 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1192 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001193 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001194
Dan Albert8b72aef2015-03-23 19:13:21 -07001195 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001196 data = source.ReadRangeSet(ranges)
1197 ctx = sha1()
1198
1199 for p in data:
1200 ctx.update(p)
1201
1202 return ctx.hexdigest()
1203
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001204 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001205 r = rangelib.RangeSet((0, 1))
1206 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001207
1208 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1209 'abort("%s has been remounted R/W; '
1210 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001211 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001212 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001213
1214DataImage = blockimgdiff.DataImage
1215
1216
Doug Zongker96a57e72010-09-26 14:57:41 -07001217# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001218PARTITION_TYPES = {
1219 "yaffs2": "MTD",
1220 "mtd": "MTD",
1221 "ext4": "EMMC",
1222 "emmc": "EMMC",
1223 "f2fs": "EMMC"
1224}
Doug Zongker96a57e72010-09-26 14:57:41 -07001225
1226def GetTypeAndDevice(mount_point, info):
1227 fstab = info["fstab"]
1228 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001229 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1230 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001231 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001232 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001233
1234
1235def ParseCertificate(data):
1236 """Parse a PEM-format certificate."""
1237 cert = []
1238 save = False
1239 for line in data.split("\n"):
1240 if "--END CERTIFICATE--" in line:
1241 break
1242 if save:
1243 cert.append(line)
1244 if "--BEGIN CERTIFICATE--" in line:
1245 save = True
1246 cert = "".join(cert).decode('base64')
1247 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001248
Doug Zongker412c02f2014-02-13 10:58:24 -08001249def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1250 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001251 """Generate a binary patch that creates the recovery image starting
1252 with the boot image. (Most of the space in these images is just the
1253 kernel, which is identical for the two, so the resulting patch
1254 should be efficient.) Add it to the output zip, along with a shell
1255 script that is run from init.rc on first boot to actually do the
1256 patching and install the new recovery image.
1257
1258 recovery_img and boot_img should be File objects for the
1259 corresponding images. info should be the dictionary returned by
1260 common.LoadInfoDict() on the input target_files.
1261 """
1262
Doug Zongker412c02f2014-02-13 10:58:24 -08001263 if info_dict is None:
1264 info_dict = OPTIONS.info_dict
1265
Doug Zongkerc9253822014-02-04 12:17:58 -08001266 diff_program = ["imgdiff"]
1267 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1268 if os.path.exists(path):
1269 diff_program.append("-b")
1270 diff_program.append(path)
1271 bonus_args = "-b /system/etc/recovery-resource.dat"
1272 else:
1273 bonus_args = ""
1274
1275 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1276 _, _, patch = d.ComputePatch()
1277 output_sink("recovery-from-boot.p", patch)
1278
Dan Albertebb19aa2015-03-27 19:11:53 -07001279 try:
1280 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1281 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1282 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001283 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001284
1285 sh = """#!/system/bin/sh
1286if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1287 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"
1288else
1289 log -t recovery "Recovery image already installed"
1290fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001291""" % {'boot_size': boot_img.size,
1292 'boot_sha1': boot_img.sha1,
1293 'recovery_size': recovery_img.size,
1294 'recovery_sha1': recovery_img.sha1,
1295 'boot_type': boot_type,
1296 'boot_device': boot_device,
1297 'recovery_type': recovery_type,
1298 'recovery_device': recovery_device,
1299 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001300
1301 # The install script location moved from /system/etc to /system/bin
1302 # in the L release. Parse the init.rc file to find out where the
1303 # target-files expects it to be, and put it there.
1304 sh_location = "etc/install-recovery.sh"
1305 try:
1306 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1307 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001308 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001309 if m:
1310 sh_location = m.group(1)
1311 print "putting script in", sh_location
1312 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001313 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001314 print "failed to read init.rc: %s" % (e,)
1315
1316 output_sink(sh_location, sh)