blob: 2c6d31773eae8a97c59862b7bbb7c7488fda943d [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
Tao Bao2ed665a2015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Baligh Uddin852a5b52014-11-20 09:52:05 -080052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.verbose = False
55 self.tempfiles = []
56 self.device_specific = None
57 self.extras = {}
58 self.info_dict = None
59 self.worker_threads = None
60
61
62OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070063
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080064
65# Values for "certificate" in apkcerts that mean special things.
66SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
67
68
Dan Albert8b72aef2015-03-23 19:13:21 -070069class ExternalError(RuntimeError):
70 pass
Doug Zongkereef39442009-04-02 12:14:19 -070071
72
73def Run(args, **kwargs):
74 """Create and return a subprocess.Popen object, printing the command
75 line on the terminal if -v was specified."""
76 if OPTIONS.verbose:
77 print " running: ", " ".join(args)
78 return subprocess.Popen(args, **kwargs)
79
80
Ying Wang7e6d4e42010-12-13 16:25:36 -080081def CloseInheritedPipes():
82 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
83 before doing other work."""
84 if platform.system() != "Darwin":
85 return
86 for d in range(3, 1025):
87 try:
88 stat = os.fstat(d)
89 if stat is not None:
90 pipebit = stat[0] & 0x1000
91 if pipebit != 0:
92 os.close(d)
93 except OSError:
94 pass
95
96
Dan Albert8b72aef2015-03-23 19:13:21 -070097def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -070098 """Read and parse the META/misc_info.txt key/value pairs from the
99 input target files and return a dict."""
100
Doug Zongkerc9253822014-02-04 12:17:58 -0800101 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700102 if isinstance(input_file, zipfile.ZipFile):
103 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 try:
107 with open(path) as f:
108 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 if e.errno == errno.ENOENT:
111 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700112 d = {}
113 try:
Michael Runge6e836112014-04-15 17:40:21 -0700114 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700115 except KeyError:
116 # ok if misc_info.txt doesn't exist
117 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700118
Doug Zongker37974732010-09-16 17:44:38 -0700119 # backwards compatibility: These values used to be in their own
120 # files. Look for them, in case we're processing an old
121 # target_files zip.
122
123 if "mkyaffs2_extra_flags" not in d:
124 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700125 d["mkyaffs2_extra_flags"] = read_helper(
126 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700127 except KeyError:
128 # ok if flags don't exist
129 pass
130
131 if "recovery_api_version" not in d:
132 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700133 d["recovery_api_version"] = read_helper(
134 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700135 except KeyError:
136 raise ValueError("can't find recovery API version in input target-files")
137
138 if "tool_extensions" not in d:
139 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800140 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700141 except KeyError:
142 # ok if extensions don't exist
143 pass
144
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800145 if "fstab_version" not in d:
146 d["fstab_version"] = "1"
147
Doug Zongker37974732010-09-16 17:44:38 -0700148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700150 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 if not line:
152 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700153 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not value:
155 continue
Doug Zongker37974732010-09-16 17:44:38 -0700156 if name == "blocksize":
157 d[name] = value
158 else:
159 d[name + "_size"] = value
160 except KeyError:
161 pass
162
163 def makeint(key):
164 if key in d:
165 d[key] = int(d[key], 0)
166
167 makeint("recovery_api_version")
168 makeint("blocksize")
169 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700170 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700171 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700172 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700173 makeint("recovery_size")
174 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800175 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700176
Doug Zongkerc9253822014-02-04 12:17:58 -0800177 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
178 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700179 return d
180
Doug Zongkerc9253822014-02-04 12:17:58 -0800181def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800183 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184 except KeyError:
185 print "Warning: could not find SYSTEM/build.prop in %s" % zip
186 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700187 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188
Michael Runge6e836112014-04-15 17:40:21 -0700189def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700190 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700191 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700192 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700193 if not line or line.startswith("#"):
194 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700195 if "=" in line:
196 name, value = line.split("=", 1)
197 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700198 return d
199
Doug Zongkerc9253822014-02-04 12:17:58 -0800200def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700201 class Partition(object):
Dan Albert8b72aef2015-03-23 19:13:21 -0700202 def __init__(self, mount_point, fs_type, device, length, device2):
203 self.mount_point = mount_point
204 self.fs_type = fs_type
205 self.device = device
206 self.length = length
207 self.device2 = device2
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700208
209 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800210 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700211 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800212 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700213 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800215 if fstab_version == 1:
216 d = {}
217 for line in data.split("\n"):
218 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700219 if not line or line.startswith("#"):
220 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800221 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800223 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 options = None
225 if len(pieces) >= 4:
226 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700227 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 if len(pieces) >= 5:
229 options = pieces[4]
230 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800233 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700235
Dan Albert8b72aef2015-03-23 19:13:21 -0700236 mount_point = pieces[0]
237 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 if options:
239 options = options.split(",")
240 for i in options:
241 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700242 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800243 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800245
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
247 device=pieces[2], length=length,
248 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
250 elif fstab_version == 2:
251 d = {}
252 for line in data.split("\n"):
253 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 if not line or line.startswith("#"):
255 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800256 pieces = line.split()
257 if len(pieces) != 5:
258 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
259
260 # Ignore entries that are managed by vold
261 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700262 if "voldmanaged=" in options:
263 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800264
265 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800267 options = options.split(",")
268 for i in options:
269 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700270 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800271 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800272 # Ignore all unknown options in the unified fstab
273 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800274
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 mount_point = pieces[1]
276 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
277 device=pieces[0], length=length, device2=None)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800278
279 else:
280 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
281
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700282 return d
283
284
Doug Zongker37974732010-09-16 17:44:38 -0700285def DumpInfoDict(d):
286 for k, v in sorted(d.items()):
287 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289
Doug Zongkerd5131602012-08-02 14:46:42 -0700290def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700291 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700292 'sourcedir'), and turn them into a boot image. Return the image
293 data, or None if sourcedir does not appear to contains files for
294 building the requested image."""
295
296 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
297 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
298 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700299
Doug Zongkerd5131602012-08-02 14:46:42 -0700300 if info_dict is None:
301 info_dict = OPTIONS.info_dict
302
Doug Zongkereef39442009-04-02 12:14:19 -0700303 ramdisk_img = tempfile.NamedTemporaryFile()
304 img = tempfile.NamedTemporaryFile()
305
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700306 if os.access(fs_config_file, os.F_OK):
307 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
308 else:
309 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
310 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700311 p2 = Run(["minigzip"],
312 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700313
314 p2.wait()
315 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
317 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700318
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800319 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
320 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
321
322 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700323
Benoit Fradina45a8682014-07-14 21:00:43 +0200324 fn = os.path.join(sourcedir, "second")
325 if os.access(fn, os.F_OK):
326 cmd.append("--second")
327 cmd.append(fn)
328
Doug Zongker171f1cd2009-06-15 22:36:37 -0700329 fn = os.path.join(sourcedir, "cmdline")
330 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700331 cmd.append("--cmdline")
332 cmd.append(open(fn).read().rstrip("\n"))
333
334 fn = os.path.join(sourcedir, "base")
335 if os.access(fn, os.F_OK):
336 cmd.append("--base")
337 cmd.append(open(fn).read().rstrip("\n"))
338
Ying Wang4de6b5b2010-08-25 14:29:34 -0700339 fn = os.path.join(sourcedir, "pagesize")
340 if os.access(fn, os.F_OK):
341 cmd.append("--pagesize")
342 cmd.append(open(fn).read().rstrip("\n"))
343
Doug Zongkerd5131602012-08-02 14:46:42 -0700344 args = info_dict.get("mkbootimg_args", None)
345 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700346 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700347
Tao Baod95e9fd2015-03-29 23:07:41 -0700348 img_unsigned = None
349 if info_dict.get("vboot", None):
350 img_unsigned = tempfile.NamedTemporaryFile()
351 cmd.extend(["--ramdisk", ramdisk_img.name,
352 "--output", img_unsigned.name])
353 else:
354 cmd.extend(["--ramdisk", ramdisk_img.name,
355 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700356
357 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700358 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700359 assert p.returncode == 0, "mkbootimg of %s image failed" % (
360 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700361
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700362 if info_dict.get("verity_key", None):
363 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin852a5b52014-11-20 09:52:05 -0800364 cmd = [OPTIONS.boot_signer_path, path, img.name,
365 info_dict["verity_key"] + ".pk8",
Dan Albert8b72aef2015-03-23 19:13:21 -0700366 info_dict["verity_key"] + ".x509.pem", img.name]
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700367 p = Run(cmd, stdout=subprocess.PIPE)
368 p.communicate()
369 assert p.returncode == 0, "boot_signer of %s image failed" % path
370
Tao Baod95e9fd2015-03-29 23:07:41 -0700371 # Sign the image if vboot is non-empty.
372 elif info_dict.get("vboot", None):
373 path = "/" + os.path.basename(sourcedir).lower()
374 img_keyblock = tempfile.NamedTemporaryFile()
375 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
376 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
377 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
378 img.name]
379 p = Run(cmd, stdout=subprocess.PIPE)
380 p.communicate()
381 assert p.returncode == 0, "vboot_signer of %s image failed" % path
382
Tao Bao2ed665a2015-04-01 11:21:55 -0700383 # Clean up the temp files.
384 img_unsigned.close()
385 img_keyblock.close()
386
Doug Zongkereef39442009-04-02 12:14:19 -0700387 img.seek(os.SEEK_SET, 0)
388 data = img.read()
389
390 ramdisk_img.close()
391 img.close()
392
393 return data
394
395
Doug Zongkerd5131602012-08-02 14:46:42 -0700396def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
397 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800398 """Return a File object (with name 'name') with the desired bootable
399 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700400 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
401 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800402 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700403
Doug Zongker55d93282011-01-25 17:03:34 -0800404 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
405 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700406 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800407 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700408
409 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
410 if os.path.exists(prebuilt_path):
411 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
412 return File.FromLocalFile(name, prebuilt_path)
413
414 print "building image from target_files %s..." % (tree_subdir,)
415 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
416 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
417 os.path.join(unpack_dir, fs_config),
418 info_dict)
419 if data:
420 return File(name, data)
421 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800422
Doug Zongkereef39442009-04-02 12:14:19 -0700423
Doug Zongker75f17362009-12-08 13:46:44 -0800424def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800425 """Unzip the given archive into a temporary directory and return the name.
426
427 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
428 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
429
430 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
431 main file), open for reading.
432 """
Doug Zongkereef39442009-04-02 12:14:19 -0700433
434 tmp = tempfile.mkdtemp(prefix="targetfiles-")
435 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800436
437 def unzip_to_dir(filename, dirname):
438 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
439 if pattern is not None:
440 cmd.append(pattern)
441 p = Run(cmd, stdout=subprocess.PIPE)
442 p.communicate()
443 if p.returncode != 0:
444 raise ExternalError("failed to unzip input target-files \"%s\"" %
445 (filename,))
446
447 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
448 if m:
449 unzip_to_dir(m.group(1), tmp)
450 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
451 filename = m.group(1)
452 else:
453 unzip_to_dir(filename, tmp)
454
455 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700456
457
458def GetKeyPasswords(keylist):
459 """Given a list of keys, prompt the user to enter passwords for
460 those which require them. Return a {key: password} dict. password
461 will be None if the key has no password."""
462
Doug Zongker8ce7c252009-05-22 13:34:54 -0700463 no_passwords = []
464 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700465 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700466 devnull = open("/dev/null", "w+b")
467 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800468 # We don't need a password for things that aren't really keys.
469 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700470 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700471 continue
472
T.R. Fullhart37e10522013-03-18 10:31:26 -0700473 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700474 "-inform", "DER", "-nocrypt"],
475 stdin=devnull.fileno(),
476 stdout=devnull.fileno(),
477 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700478 p.communicate()
479 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700480 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700481 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700482 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700483 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
484 "-inform", "DER", "-passin", "pass:"],
485 stdin=devnull.fileno(),
486 stdout=devnull.fileno(),
487 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700488 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700489 if p.returncode == 0:
490 # Encrypted key with empty string as password.
491 key_passwords[k] = ''
492 elif stderr.startswith('Error decrypting key'):
493 # Definitely encrypted key.
494 # It would have said "Error reading key" if it didn't parse correctly.
495 need_passwords.append(k)
496 else:
497 # Potentially, a type of key that openssl doesn't understand.
498 # We'll let the routines in signapk.jar handle it.
499 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700500 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700501
T.R. Fullhart37e10522013-03-18 10:31:26 -0700502 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700503 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700504 return key_passwords
505
506
Doug Zongker951495f2009-08-14 12:44:19 -0700507def SignFile(input_name, output_name, key, password, align=None,
508 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700509 """Sign the input_name zip/jar/apk, producing output_name. Use the
510 given key and password (the latter may be None if the key does not
511 have a password.
512
513 If align is an integer > 1, zipalign is run to align stored files in
514 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700515
516 If whole_file is true, use the "-w" option to SignApk to embed a
517 signature that covers the whole file in the archive comment of the
518 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700519 """
Doug Zongker951495f2009-08-14 12:44:19 -0700520
Doug Zongkereef39442009-04-02 12:14:19 -0700521 if align == 0 or align == 1:
522 align = None
523
524 if align:
525 temp = tempfile.NamedTemporaryFile()
526 sign_name = temp.name
527 else:
528 sign_name = output_name
529
Baligh Uddin339ee492014-09-05 11:18:07 -0700530 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700531 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
532 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700533 if whole_file:
534 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 cmd.extend([key + OPTIONS.public_key_suffix,
536 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700537 input_name, sign_name])
538
539 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700540 if password is not None:
541 password += "\n"
542 p.communicate(password)
543 if p.returncode != 0:
544 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
545
546 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700547 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700548 p.communicate()
549 if p.returncode != 0:
550 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
551 temp.close()
552
553
Doug Zongker37974732010-09-16 17:44:38 -0700554def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700555 """Check the data string passed against the max size limit, if
556 any, for the given target. Raise exception if the data is too big.
557 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700558
Dan Albert8b72aef2015-03-23 19:13:21 -0700559 if target.endswith(".img"):
560 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700561 mount_point = "/" + target
562
Ying Wangf8824af2014-06-03 14:07:27 -0700563 fs_type = None
564 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700565 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700566 if mount_point == "/userdata":
567 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700568 p = info_dict["fstab"][mount_point]
569 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800570 device = p.device
571 if "/" in device:
572 device = device[device.rfind("/")+1:]
573 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700574 if not fs_type or not limit:
575 return
Doug Zongkereef39442009-04-02 12:14:19 -0700576
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700577 if fs_type == "yaffs2":
578 # image size should be increased by 1/64th to account for the
579 # spare area (64 bytes per 2k page)
580 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800581 size = len(data)
582 pct = float(size) * 100.0 / limit
583 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
584 if pct >= 99.0:
585 raise ExternalError(msg)
586 elif pct >= 95.0:
587 print
588 print " WARNING: ", msg
589 print
590 elif OPTIONS.verbose:
591 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700592
593
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800594def ReadApkCerts(tf_zip):
595 """Given a target_files ZipFile, parse the META/apkcerts.txt file
596 and return a {package: cert} dict."""
597 certmap = {}
598 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
599 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700600 if not line:
601 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800602 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
603 r'private_key="(.*)"$', line)
604 if m:
605 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700606 public_key_suffix_len = len(OPTIONS.public_key_suffix)
607 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800608 if cert in SPECIAL_CERT_STRINGS and not privkey:
609 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700610 elif (cert.endswith(OPTIONS.public_key_suffix) and
611 privkey.endswith(OPTIONS.private_key_suffix) and
612 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
613 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800614 else:
615 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
616 return certmap
617
618
Doug Zongkereef39442009-04-02 12:14:19 -0700619COMMON_DOCSTRING = """
620 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700621 Prepend <dir>/bin to the list of places to search for binaries
622 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700623
Doug Zongker05d3dea2009-06-22 11:32:31 -0700624 -s (--device_specific) <file>
625 Path to the python module containing device-specific
626 releasetools code.
627
Doug Zongker8bec09e2009-11-30 15:37:14 -0800628 -x (--extra) <key=value>
629 Add a key/value pair to the 'extras' dict, which device-specific
630 extension code may look at.
631
Doug Zongkereef39442009-04-02 12:14:19 -0700632 -v (--verbose)
633 Show command lines being executed.
634
635 -h (--help)
636 Display this usage message and exit.
637"""
638
639def Usage(docstring):
640 print docstring.rstrip("\n")
641 print COMMON_DOCSTRING
642
643
644def ParseOptions(argv,
645 docstring,
646 extra_opts="", extra_long_opts=(),
647 extra_option_handler=None):
648 """Parse the options in argv and return any arguments that aren't
649 flags. docstring is the calling module's docstring, to be displayed
650 for errors and -h. extra_opts and extra_long_opts are for flags
651 defined by the caller, which are processed by passing them to
652 extra_option_handler."""
653
654 try:
655 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800656 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700657 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700658 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin852a5b52014-11-20 09:52:05 -0800659 "private_key_suffix=", "boot_signer_path=", "device_specific=",
660 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700661 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700662 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700663 Usage(docstring)
664 print "**", str(err), "**"
665 sys.exit(2)
666
Doug Zongkereef39442009-04-02 12:14:19 -0700667 for o, a in opts:
668 if o in ("-h", "--help"):
669 Usage(docstring)
670 sys.exit()
671 elif o in ("-v", "--verbose"):
672 OPTIONS.verbose = True
673 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700674 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700675 elif o in ("--signapk_path",):
676 OPTIONS.signapk_path = a
677 elif o in ("--extra_signapk_args",):
678 OPTIONS.extra_signapk_args = shlex.split(a)
679 elif o in ("--java_path",):
680 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700681 elif o in ("--java_args",):
682 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700683 elif o in ("--public_key_suffix",):
684 OPTIONS.public_key_suffix = a
685 elif o in ("--private_key_suffix",):
686 OPTIONS.private_key_suffix = a
Baligh Uddin852a5b52014-11-20 09:52:05 -0800687 elif o in ("--boot_signer_path",):
688 OPTIONS.boot_signer_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700689 elif o in ("-s", "--device_specific"):
690 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800691 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800692 key, value = a.split("=", 1)
693 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700694 else:
695 if extra_option_handler is None or not extra_option_handler(o, a):
696 assert False, "unknown option \"%s\"" % (o,)
697
Doug Zongker85448772014-09-09 14:59:20 -0700698 if OPTIONS.search_path:
699 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
700 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700701
702 return args
703
704
Doug Zongkerfc44a512014-08-26 13:10:25 -0700705def MakeTempFile(prefix=None, suffix=None):
706 """Make a temp file and add it to the list of things to be deleted
707 when Cleanup() is called. Return the filename."""
708 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
709 os.close(fd)
710 OPTIONS.tempfiles.append(fn)
711 return fn
712
713
Doug Zongkereef39442009-04-02 12:14:19 -0700714def Cleanup():
715 for i in OPTIONS.tempfiles:
716 if os.path.isdir(i):
717 shutil.rmtree(i)
718 else:
719 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700720
721
722class PasswordManager(object):
723 def __init__(self):
724 self.editor = os.getenv("EDITOR", None)
725 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
726
727 def GetPasswords(self, items):
728 """Get passwords corresponding to each string in 'items',
729 returning a dict. (The dict may have keys in addition to the
730 values in 'items'.)
731
732 Uses the passwords in $ANDROID_PW_FILE if available, letting the
733 user edit that file to add more needed passwords. If no editor is
734 available, or $ANDROID_PW_FILE isn't define, prompts the user
735 interactively in the ordinary way.
736 """
737
738 current = self.ReadFile()
739
740 first = True
741 while True:
742 missing = []
743 for i in items:
744 if i not in current or not current[i]:
745 missing.append(i)
746 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700747 if not missing:
748 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700749
750 for i in missing:
751 current[i] = ""
752
753 if not first:
754 print "key file %s still missing some passwords." % (self.pwfile,)
755 answer = raw_input("try to edit again? [y]> ").strip()
756 if answer and answer[0] not in 'yY':
757 raise RuntimeError("key passwords unavailable")
758 first = False
759
760 current = self.UpdateAndReadFile(current)
761
Dan Albert8b72aef2015-03-23 19:13:21 -0700762 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700763 """Prompt the user to enter a value (password) for each key in
764 'current' whose value is fales. Returns a new dict with all the
765 values.
766 """
767 result = {}
768 for k, v in sorted(current.iteritems()):
769 if v:
770 result[k] = v
771 else:
772 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700773 result[k] = getpass.getpass(
774 "Enter password for %s key> " % k).strip()
775 if result[k]:
776 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700777 return result
778
779 def UpdateAndReadFile(self, current):
780 if not self.editor or not self.pwfile:
781 return self.PromptResult(current)
782
783 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700784 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700785 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
786 f.write("# (Additional spaces are harmless.)\n\n")
787
788 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700789 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
790 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700791 f.write("[[[ %s ]]] %s\n" % (v, k))
792 if not v and first_line is None:
793 # position cursor on first line with no password.
794 first_line = i + 4
795 f.close()
796
797 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
798 _, _ = p.communicate()
799
800 return self.ReadFile()
801
802 def ReadFile(self):
803 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700804 if self.pwfile is None:
805 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700806 try:
807 f = open(self.pwfile, "r")
808 for line in f:
809 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700810 if not line or line[0] == '#':
811 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
813 if not m:
814 print "failed to parse password file: ", line
815 else:
816 result[m.group(2)] = m.group(1)
817 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700818 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700819 if e.errno != errno.ENOENT:
820 print "error reading password file: ", str(e)
821 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700822
823
Dan Albert8e0178d2015-01-27 15:53:15 -0800824def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
825 compress_type=None):
826 import datetime
827
828 # http://b/18015246
829 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
830 # for files larger than 2GiB. We can work around this by adjusting their
831 # limit. Note that `zipfile.writestr()` will not work for strings larger than
832 # 2GiB. The Python interpreter sometimes rejects strings that large (though
833 # it isn't clear to me exactly what circumstances cause this).
834 # `zipfile.write()` must be used directly to work around this.
835 #
836 # This mess can be avoided if we port to python3.
837 saved_zip64_limit = zipfile.ZIP64_LIMIT
838 zipfile.ZIP64_LIMIT = (1 << 32) - 1
839
840 if compress_type is None:
841 compress_type = zip_file.compression
842 if arcname is None:
843 arcname = filename
844
845 saved_stat = os.stat(filename)
846
847 try:
848 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
849 # file to be zipped and reset it when we're done.
850 os.chmod(filename, perms)
851
852 # Use a fixed timestamp so the output is repeatable.
853 epoch = datetime.datetime.fromtimestamp(0)
854 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
855 os.utime(filename, (timestamp, timestamp))
856
857 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
858 finally:
859 os.chmod(filename, saved_stat.st_mode)
860 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
861 zipfile.ZIP64_LIMIT = saved_zip64_limit
862
863
Tao Bao2ed665a2015-04-01 11:21:55 -0700864def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=0o644,
865 compress_type=None):
866 """Wrap zipfile.writestr() function to work around the zip64 limit.
867
868 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
869 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
870 when calling crc32(bytes).
871
872 But it still works fine to write a shorter string into a large zip file.
873 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
874 when we know the string won't be too long.
875 """
876
877 saved_zip64_limit = zipfile.ZIP64_LIMIT
878 zipfile.ZIP64_LIMIT = (1 << 32) - 1
879
880 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
881 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700882 zinfo.compress_type = zip_file.compression
Geremy Condra36bd3652014-02-06 19:45:10 -0800883 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700884 zinfo = zinfo_or_arcname
885
886 # If compress_type is given, it overrides the value in zinfo.
887 if compress_type is not None:
888 zinfo.compress_type = compress_type
889
890 # Use a fixed timestamp so the output is repeatable.
Doug Zongker048e7ca2009-06-15 14:31:53 -0700891 zinfo.external_attr = perms << 16
Tao Bao2ed665a2015-04-01 11:21:55 -0700892 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
893
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 zip_file.writestr(zinfo, data)
Tao Bao2ed665a2015-04-01 11:21:55 -0700895 zipfile.ZIP64_LIMIT = saved_zip64_limit
896
897
898def ZipClose(zip_file):
899 # http://b/18015246
900 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
901 # central directory.
902 saved_zip64_limit = zipfile.ZIP64_LIMIT
903 zipfile.ZIP64_LIMIT = (1 << 32) - 1
904
905 zip_file.close()
906
907 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700908
909
910class DeviceSpecificParams(object):
911 module = None
912 def __init__(self, **kwargs):
913 """Keyword arguments to the constructor become attributes of this
914 object, which is passed to all functions in the device-specific
915 module."""
916 for k, v in kwargs.iteritems():
917 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800918 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700919
920 if self.module is None:
921 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700922 if not path:
923 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700924 try:
925 if os.path.isdir(path):
926 info = imp.find_module("releasetools", [path])
927 else:
928 d, f = os.path.split(path)
929 b, x = os.path.splitext(f)
930 if x == ".py":
931 f = b
932 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800933 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700934 self.module = imp.load_module("device_specific", *info)
935 except ImportError:
936 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700937
938 def _DoCall(self, function_name, *args, **kwargs):
939 """Call the named function in the device-specific module, passing
940 the given args and kwargs. The first argument to the call will be
941 the DeviceSpecific object itself. If there is no module, or the
942 module does not define the function, return the value of the
943 'default' kwarg (which itself defaults to None)."""
944 if self.module is None or not hasattr(self.module, function_name):
945 return kwargs.get("default", None)
946 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
947
948 def FullOTA_Assertions(self):
949 """Called after emitting the block of assertions at the top of a
950 full OTA package. Implementations can add whatever additional
951 assertions they like."""
952 return self._DoCall("FullOTA_Assertions")
953
Doug Zongkere5ff5902012-01-17 10:55:37 -0800954 def FullOTA_InstallBegin(self):
955 """Called at the start of full OTA installation."""
956 return self._DoCall("FullOTA_InstallBegin")
957
Doug Zongker05d3dea2009-06-22 11:32:31 -0700958 def FullOTA_InstallEnd(self):
959 """Called at the end of full OTA installation; typically this is
960 used to install the image for the device's baseband processor."""
961 return self._DoCall("FullOTA_InstallEnd")
962
963 def IncrementalOTA_Assertions(self):
964 """Called after emitting the block of assertions at the top of an
965 incremental OTA package. Implementations can add whatever
966 additional assertions they like."""
967 return self._DoCall("IncrementalOTA_Assertions")
968
Doug Zongkere5ff5902012-01-17 10:55:37 -0800969 def IncrementalOTA_VerifyBegin(self):
970 """Called at the start of the verification phase of incremental
971 OTA installation; additional checks can be placed here to abort
972 the script before any changes are made."""
973 return self._DoCall("IncrementalOTA_VerifyBegin")
974
Doug Zongker05d3dea2009-06-22 11:32:31 -0700975 def IncrementalOTA_VerifyEnd(self):
976 """Called at the end of the verification phase of incremental OTA
977 installation; additional checks can be placed here to abort the
978 script before any changes are made."""
979 return self._DoCall("IncrementalOTA_VerifyEnd")
980
Doug Zongkere5ff5902012-01-17 10:55:37 -0800981 def IncrementalOTA_InstallBegin(self):
982 """Called at the start of incremental OTA installation (after
983 verification is complete)."""
984 return self._DoCall("IncrementalOTA_InstallBegin")
985
Doug Zongker05d3dea2009-06-22 11:32:31 -0700986 def IncrementalOTA_InstallEnd(self):
987 """Called at the end of incremental OTA installation; typically
988 this is used to install the image for the device's baseband
989 processor."""
990 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700991
992class File(object):
993 def __init__(self, name, data):
994 self.name = name
995 self.data = data
996 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -0800997 self.sha1 = sha1(data).hexdigest()
998
999 @classmethod
1000 def FromLocalFile(cls, name, diskname):
1001 f = open(diskname, "rb")
1002 data = f.read()
1003 f.close()
1004 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001005
1006 def WriteToTemp(self):
1007 t = tempfile.NamedTemporaryFile()
1008 t.write(self.data)
1009 t.flush()
1010 return t
1011
Geremy Condra36bd3652014-02-06 19:45:10 -08001012 def AddToZip(self, z, compression=None):
Tao Bao2ed665a2015-04-01 11:21:55 -07001013 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001014
1015DIFF_PROGRAM_BY_EXT = {
1016 ".gz" : "imgdiff",
1017 ".zip" : ["imgdiff", "-z"],
1018 ".jar" : ["imgdiff", "-z"],
1019 ".apk" : ["imgdiff", "-z"],
1020 ".img" : "imgdiff",
1021 }
1022
1023class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001024 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001025 self.tf = tf
1026 self.sf = sf
1027 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001028 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001029
1030 def ComputePatch(self):
1031 """Compute the patch (as a string of data) needed to turn sf into
1032 tf. Returns the same tuple as GetPatch()."""
1033
1034 tf = self.tf
1035 sf = self.sf
1036
Doug Zongker24cd2802012-08-14 16:36:15 -07001037 if self.diff_program:
1038 diff_program = self.diff_program
1039 else:
1040 ext = os.path.splitext(tf.name)[1]
1041 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001042
1043 ttemp = tf.WriteToTemp()
1044 stemp = sf.WriteToTemp()
1045
1046 ext = os.path.splitext(tf.name)[1]
1047
1048 try:
1049 ptemp = tempfile.NamedTemporaryFile()
1050 if isinstance(diff_program, list):
1051 cmd = copy.copy(diff_program)
1052 else:
1053 cmd = [diff_program]
1054 cmd.append(stemp.name)
1055 cmd.append(ttemp.name)
1056 cmd.append(ptemp.name)
1057 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001058 err = []
1059 def run():
1060 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001061 if e:
1062 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001063 th = threading.Thread(target=run)
1064 th.start()
1065 th.join(timeout=300) # 5 mins
1066 if th.is_alive():
1067 print "WARNING: diff command timed out"
1068 p.terminate()
1069 th.join(5)
1070 if th.is_alive():
1071 p.kill()
1072 th.join()
1073
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001074 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001075 print "WARNING: failure running %s:\n%s\n" % (
1076 diff_program, "".join(err))
1077 self.patch = None
1078 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001079 diff = ptemp.read()
1080 finally:
1081 ptemp.close()
1082 stemp.close()
1083 ttemp.close()
1084
1085 self.patch = diff
1086 return self.tf, self.sf, self.patch
1087
1088
1089 def GetPatch(self):
1090 """Return a tuple (target_file, source_file, patch_data).
1091 patch_data may be None if ComputePatch hasn't been called, or if
1092 computing the patch failed."""
1093 return self.tf, self.sf, self.patch
1094
1095
1096def ComputeDifferences(diffs):
1097 """Call ComputePatch on all the Difference objects in 'diffs'."""
1098 print len(diffs), "diffs to compute"
1099
1100 # Do the largest files first, to try and reduce the long-pole effect.
1101 by_size = [(i.tf.size, i) for i in diffs]
1102 by_size.sort(reverse=True)
1103 by_size = [i[1] for i in by_size]
1104
1105 lock = threading.Lock()
1106 diff_iter = iter(by_size) # accessed under lock
1107
1108 def worker():
1109 try:
1110 lock.acquire()
1111 for d in diff_iter:
1112 lock.release()
1113 start = time.time()
1114 d.ComputePatch()
1115 dur = time.time() - start
1116 lock.acquire()
1117
1118 tf, sf, patch = d.GetPatch()
1119 if sf.name == tf.name:
1120 name = tf.name
1121 else:
1122 name = "%s (%s)" % (tf.name, sf.name)
1123 if patch is None:
1124 print "patching failed! %s" % (name,)
1125 else:
1126 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1127 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1128 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001129 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001130 print e
1131 raise
1132
1133 # start worker threads; wait for them all to finish.
1134 threads = [threading.Thread(target=worker)
1135 for i in range(OPTIONS.worker_threads)]
1136 for th in threads:
1137 th.start()
1138 while threads:
1139 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001140
1141
Dan Albert8b72aef2015-03-23 19:13:21 -07001142class BlockDifference(object):
1143 def __init__(self, partition, tgt, src=None, check_first_block=False,
1144 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001145 self.tgt = tgt
1146 self.src = src
1147 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001148 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001149
Tao Baodd2a5892015-03-12 12:32:37 -07001150 if version is None:
1151 version = 1
1152 if OPTIONS.info_dict:
1153 version = max(
1154 int(i) for i in
1155 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1156 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001157
1158 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001159 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001160 tmpdir = tempfile.mkdtemp()
1161 OPTIONS.tempfiles.append(tmpdir)
1162 self.path = os.path.join(tmpdir, partition)
1163 b.Compute(self.path)
1164
1165 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1166
1167 def WriteScript(self, script, output_zip, progress=None):
1168 if not self.src:
1169 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001170 script.Print("Patching %s image unconditionally..." % (self.partition,))
1171 else:
1172 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001173
Dan Albert8b72aef2015-03-23 19:13:21 -07001174 if progress:
1175 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001176 self._WriteUpdate(script, output_zip)
1177
1178 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001179 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001180 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001181 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001182 else:
Michael Runge910b0052015-02-11 19:28:08 -08001183 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001184 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1185 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001186 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001187 '"%s.new.dat", "%s.patch.dat")) then') % (
1188 self.device, self.src.care_map.to_string_raw(),
1189 self.src.TotalSha1(),
1190 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001191 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001192 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1193 self.device, self.src.care_map.to_string_raw(),
1194 self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001195 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001196 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001197
Tao Baodd2a5892015-03-12 12:32:37 -07001198 # When generating incrementals for the system and vendor partitions,
1199 # explicitly check the first block (which contains the superblock) of
1200 # the partition to see if it's what we expect. If this check fails,
1201 # give an explicit log message about the partition having been
1202 # remounted R/W (the most likely explanation) and the need to flash to
1203 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001204 if self.check_first_block:
1205 self._CheckFirstBlock(script)
1206
Tao Baodd2a5892015-03-12 12:32:37 -07001207 # Abort the OTA update. Note that the incremental OTA cannot be applied
1208 # even if it may match the checksum of the target partition.
1209 # a) If version < 3, operations like move and erase will make changes
1210 # unconditionally and damage the partition.
1211 # b) If version >= 3, it won't even reach here.
1212 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1213 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001214
1215 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001216 ZipWrite(output_zip,
1217 '{}.transfer.list'.format(self.path),
1218 '{}.transfer.list'.format(self.partition))
1219 ZipWrite(output_zip,
1220 '{}.new.dat'.format(self.path),
1221 '{}.new.dat'.format(self.partition))
1222 ZipWrite(output_zip,
1223 '{}.patch.dat'.format(self.path),
1224 '{}.patch.dat'.format(self.partition),
1225 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001226
Dan Albert8e0178d2015-01-27 15:53:15 -08001227 call = ('block_image_update("{device}", '
1228 'package_extract_file("{partition}.transfer.list"), '
1229 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1230 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001231 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001232
Dan Albert8b72aef2015-03-23 19:13:21 -07001233 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001234 data = source.ReadRangeSet(ranges)
1235 ctx = sha1()
1236
1237 for p in data:
1238 ctx.update(p)
1239
1240 return ctx.hexdigest()
1241
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001242 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001243 r = rangelib.RangeSet((0, 1))
1244 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001245
1246 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1247 'abort("%s has been remounted R/W; '
1248 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001249 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001250 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001251
1252DataImage = blockimgdiff.DataImage
1253
1254
Doug Zongker96a57e72010-09-26 14:57:41 -07001255# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001256PARTITION_TYPES = {
1257 "yaffs2": "MTD",
1258 "mtd": "MTD",
1259 "ext4": "EMMC",
1260 "emmc": "EMMC",
Mohamad Ayyasha9905342015-05-01 15:39:36 -07001261 "f2fs": "EMMC",
1262 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001263}
Doug Zongker96a57e72010-09-26 14:57:41 -07001264
1265def GetTypeAndDevice(mount_point, info):
1266 fstab = info["fstab"]
1267 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1269 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001270 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001271 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001272
1273
1274def ParseCertificate(data):
1275 """Parse a PEM-format certificate."""
1276 cert = []
1277 save = False
1278 for line in data.split("\n"):
1279 if "--END CERTIFICATE--" in line:
1280 break
1281 if save:
1282 cert.append(line)
1283 if "--BEGIN CERTIFICATE--" in line:
1284 save = True
1285 cert = "".join(cert).decode('base64')
1286 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001287
Doug Zongker412c02f2014-02-13 10:58:24 -08001288def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1289 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001290 """Generate a binary patch that creates the recovery image starting
1291 with the boot image. (Most of the space in these images is just the
1292 kernel, which is identical for the two, so the resulting patch
1293 should be efficient.) Add it to the output zip, along with a shell
1294 script that is run from init.rc on first boot to actually do the
1295 patching and install the new recovery image.
1296
1297 recovery_img and boot_img should be File objects for the
1298 corresponding images. info should be the dictionary returned by
1299 common.LoadInfoDict() on the input target_files.
1300 """
1301
Doug Zongker412c02f2014-02-13 10:58:24 -08001302 if info_dict is None:
1303 info_dict = OPTIONS.info_dict
1304
Doug Zongkerc9253822014-02-04 12:17:58 -08001305 diff_program = ["imgdiff"]
1306 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1307 if os.path.exists(path):
1308 diff_program.append("-b")
1309 diff_program.append(path)
1310 bonus_args = "-b /system/etc/recovery-resource.dat"
1311 else:
1312 bonus_args = ""
1313
1314 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1315 _, _, patch = d.ComputePatch()
1316 output_sink("recovery-from-boot.p", patch)
1317
Dan Albertebb19aa2015-03-27 19:11:53 -07001318 try:
1319 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1320 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1321 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001322 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001323
1324 sh = """#!/system/bin/sh
1325if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1326 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"
1327else
1328 log -t recovery "Recovery image already installed"
1329fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001330""" % {'boot_size': boot_img.size,
1331 'boot_sha1': boot_img.sha1,
1332 'recovery_size': recovery_img.size,
1333 'recovery_sha1': recovery_img.sha1,
1334 'boot_type': boot_type,
1335 'boot_device': boot_device,
1336 'recovery_type': recovery_type,
1337 'recovery_device': recovery_device,
1338 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001339
1340 # The install script location moved from /system/etc to /system/bin
1341 # in the L release. Parse the init.rc file to find out where the
1342 # target-files expects it to be, and put it there.
1343 sh_location = "etc/install-recovery.sh"
1344 try:
1345 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1346 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001348 if m:
1349 sh_location = m.group(1)
1350 print "putting script in", sh_location
1351 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001352 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001353 print "failed to read init.rc: %s" % (e,)
1354
1355 output_sink(sh_location, sh)