blob: ebf47cb01d2cb8e14368d99462d05c8f23fe21e8 [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 Baof3282b42015-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"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
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):
Tao Bao548eb762015-06-10 12:32:41 -0700205 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700211 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212
213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700217 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800219 if fstab_version == 1:
220 d = {}
221 for line in data.split("\n"):
222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not line or line.startswith("#"):
224 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700226 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 options = None
229 if len(pieces) >= 4:
230 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 if len(pieces) >= 5:
233 options = pieces[4]
234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800237 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700238 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 mount_point = pieces[0]
241 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 if options:
243 options = options.split(",")
244 for i in options:
245 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
251 device=pieces[2], length=length,
252 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253
254 elif fstab_version == 2:
255 d = {}
256 for line in data.split("\n"):
257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not line or line.startswith("#"):
259 continue
Tao Bao548eb762015-06-10 12:32:41 -0700260 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 pieces = line.split()
262 if len(pieces) != 5:
263 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
264
265 # Ignore entries that are managed by vold
266 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 if "voldmanaged=" in options:
268 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269
270 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 options = options.split(",")
273 for i in options:
274 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 # Ignore all unknown options in the unified fstab
278 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800279
Tao Bao548eb762015-06-10 12:32:41 -0700280 mount_flags = pieces[3]
281 # Honor the SELinux context if present.
282 context = None
283 for i in mount_flags.split(","):
284 if i.startswith("context="):
285 context = i
286
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 mount_point = pieces[1]
288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700289 device=pieces[0], length=length,
290 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 else:
293 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
294
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295 return d
296
297
Doug Zongker37974732010-09-16 17:44:38 -0700298def DumpInfoDict(d):
299 for k, v in sorted(d.items()):
300 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301
Dan Albert8b72aef2015-03-23 19:13:21 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700304 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700305 'sourcedir'), and turn them into a boot image. Return the image
306 data, or None if sourcedir does not appear to contains files for
307 building the requested image."""
308
309 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
310 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
311 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700312
Doug Zongkerd5131602012-08-02 14:46:42 -0700313 if info_dict is None:
314 info_dict = OPTIONS.info_dict
315
Doug Zongkereef39442009-04-02 12:14:19 -0700316 ramdisk_img = tempfile.NamedTemporaryFile()
317 img = tempfile.NamedTemporaryFile()
318
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700319 if os.access(fs_config_file, os.F_OK):
320 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
321 else:
322 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
323 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700324 p2 = Run(["minigzip"],
325 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700326
327 p2.wait()
328 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
330 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700331
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800332 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
333 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
334
335 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700336
Benoit Fradina45a8682014-07-14 21:00:43 +0200337 fn = os.path.join(sourcedir, "second")
338 if os.access(fn, os.F_OK):
339 cmd.append("--second")
340 cmd.append(fn)
341
Doug Zongker171f1cd2009-06-15 22:36:37 -0700342 fn = os.path.join(sourcedir, "cmdline")
343 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700344 cmd.append("--cmdline")
345 cmd.append(open(fn).read().rstrip("\n"))
346
347 fn = os.path.join(sourcedir, "base")
348 if os.access(fn, os.F_OK):
349 cmd.append("--base")
350 cmd.append(open(fn).read().rstrip("\n"))
351
Ying Wang4de6b5b2010-08-25 14:29:34 -0700352 fn = os.path.join(sourcedir, "pagesize")
353 if os.access(fn, os.F_OK):
354 cmd.append("--pagesize")
355 cmd.append(open(fn).read().rstrip("\n"))
356
Doug Zongkerd5131602012-08-02 14:46:42 -0700357 args = info_dict.get("mkbootimg_args", None)
358 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700359 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700360
Tao Baod95e9fd2015-03-29 23:07:41 -0700361 img_unsigned = None
362 if info_dict.get("vboot", None):
363 img_unsigned = tempfile.NamedTemporaryFile()
364 cmd.extend(["--ramdisk", ramdisk_img.name,
365 "--output", img_unsigned.name])
366 else:
367 cmd.extend(["--ramdisk", ramdisk_img.name,
368 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700369
370 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700371 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700372 assert p.returncode == 0, "mkbootimg of %s image failed" % (
373 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700374
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100375 if (info_dict.get("boot_signer", None) == "true" and
376 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700377 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700378 cmd = [OPTIONS.boot_signer_path]
379 cmd.extend(OPTIONS.boot_signer_args)
380 cmd.extend([path, img.name,
381 info_dict["verity_key"] + ".pk8",
382 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700383 p = Run(cmd, stdout=subprocess.PIPE)
384 p.communicate()
385 assert p.returncode == 0, "boot_signer of %s image failed" % path
386
Tao Baod95e9fd2015-03-29 23:07:41 -0700387 # Sign the image if vboot is non-empty.
388 elif info_dict.get("vboot", None):
389 path = "/" + os.path.basename(sourcedir).lower()
390 img_keyblock = tempfile.NamedTemporaryFile()
391 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
392 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700393 info_dict["vboot_key"] + ".vbprivk",
394 info_dict["vboot_subkey"] + ".vbprivk",
395 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700396 img.name]
397 p = Run(cmd, stdout=subprocess.PIPE)
398 p.communicate()
399 assert p.returncode == 0, "vboot_signer of %s image failed" % path
400
Tao Baof3282b42015-04-01 11:21:55 -0700401 # Clean up the temp files.
402 img_unsigned.close()
403 img_keyblock.close()
404
Doug Zongkereef39442009-04-02 12:14:19 -0700405 img.seek(os.SEEK_SET, 0)
406 data = img.read()
407
408 ramdisk_img.close()
409 img.close()
410
411 return data
412
413
Doug Zongkerd5131602012-08-02 14:46:42 -0700414def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
415 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800416 """Return a File object (with name 'name') with the desired bootable
417 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700418 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
419 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800420 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700421
Doug Zongker55d93282011-01-25 17:03:34 -0800422 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
423 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700424 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800425 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700426
427 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
428 if os.path.exists(prebuilt_path):
429 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
430 return File.FromLocalFile(name, prebuilt_path)
431
432 print "building image from target_files %s..." % (tree_subdir,)
433 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
434 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
435 os.path.join(unpack_dir, fs_config),
436 info_dict)
437 if data:
438 return File(name, data)
439 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800440
Doug Zongkereef39442009-04-02 12:14:19 -0700441
Doug Zongker75f17362009-12-08 13:46:44 -0800442def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800443 """Unzip the given archive into a temporary directory and return the name.
444
445 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
446 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
447
448 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
449 main file), open for reading.
450 """
Doug Zongkereef39442009-04-02 12:14:19 -0700451
452 tmp = tempfile.mkdtemp(prefix="targetfiles-")
453 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800454
455 def unzip_to_dir(filename, dirname):
456 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
457 if pattern is not None:
458 cmd.append(pattern)
459 p = Run(cmd, stdout=subprocess.PIPE)
460 p.communicate()
461 if p.returncode != 0:
462 raise ExternalError("failed to unzip input target-files \"%s\"" %
463 (filename,))
464
465 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
466 if m:
467 unzip_to_dir(m.group(1), tmp)
468 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
469 filename = m.group(1)
470 else:
471 unzip_to_dir(filename, tmp)
472
473 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700474
475
476def GetKeyPasswords(keylist):
477 """Given a list of keys, prompt the user to enter passwords for
478 those which require them. Return a {key: password} dict. password
479 will be None if the key has no password."""
480
Doug Zongker8ce7c252009-05-22 13:34:54 -0700481 no_passwords = []
482 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700483 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700484 devnull = open("/dev/null", "w+b")
485 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800486 # We don't need a password for things that aren't really keys.
487 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700488 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700489 continue
490
T.R. Fullhart37e10522013-03-18 10:31:26 -0700491 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700492 "-inform", "DER", "-nocrypt"],
493 stdin=devnull.fileno(),
494 stdout=devnull.fileno(),
495 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700496 p.communicate()
497 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700498 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700499 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700500 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700501 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
502 "-inform", "DER", "-passin", "pass:"],
503 stdin=devnull.fileno(),
504 stdout=devnull.fileno(),
505 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700506 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700507 if p.returncode == 0:
508 # Encrypted key with empty string as password.
509 key_passwords[k] = ''
510 elif stderr.startswith('Error decrypting key'):
511 # Definitely encrypted key.
512 # It would have said "Error reading key" if it didn't parse correctly.
513 need_passwords.append(k)
514 else:
515 # Potentially, a type of key that openssl doesn't understand.
516 # We'll let the routines in signapk.jar handle it.
517 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700518 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700519
T.R. Fullhart37e10522013-03-18 10:31:26 -0700520 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700521 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700522 return key_passwords
523
524
Doug Zongker951495f2009-08-14 12:44:19 -0700525def SignFile(input_name, output_name, key, password, align=None,
526 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700527 """Sign the input_name zip/jar/apk, producing output_name. Use the
528 given key and password (the latter may be None if the key does not
529 have a password.
530
531 If align is an integer > 1, zipalign is run to align stored files in
532 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700533
534 If whole_file is true, use the "-w" option to SignApk to embed a
535 signature that covers the whole file in the archive comment of the
536 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700537 """
Doug Zongker951495f2009-08-14 12:44:19 -0700538
Doug Zongkereef39442009-04-02 12:14:19 -0700539 if align == 0 or align == 1:
540 align = None
541
542 if align:
543 temp = tempfile.NamedTemporaryFile()
544 sign_name = temp.name
545 else:
546 sign_name = output_name
547
Baligh Uddin339ee492014-09-05 11:18:07 -0700548 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700549 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
550 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700551 if whole_file:
552 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700553 cmd.extend([key + OPTIONS.public_key_suffix,
554 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700555 input_name, sign_name])
556
557 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700558 if password is not None:
559 password += "\n"
560 p.communicate(password)
561 if p.returncode != 0:
562 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
563
564 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700565 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700566 p.communicate()
567 if p.returncode != 0:
568 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
569 temp.close()
570
571
Doug Zongker37974732010-09-16 17:44:38 -0700572def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700573 """Check the data string passed against the max size limit, if
574 any, for the given target. Raise exception if the data is too big.
575 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700576
Dan Albert8b72aef2015-03-23 19:13:21 -0700577 if target.endswith(".img"):
578 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700579 mount_point = "/" + target
580
Ying Wangf8824af2014-06-03 14:07:27 -0700581 fs_type = None
582 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700583 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700584 if mount_point == "/userdata":
585 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700586 p = info_dict["fstab"][mount_point]
587 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800588 device = p.device
589 if "/" in device:
590 device = device[device.rfind("/")+1:]
591 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700592 if not fs_type or not limit:
593 return
Doug Zongkereef39442009-04-02 12:14:19 -0700594
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700595 if fs_type == "yaffs2":
596 # image size should be increased by 1/64th to account for the
597 # spare area (64 bytes per 2k page)
598 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800599 size = len(data)
600 pct = float(size) * 100.0 / limit
601 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
602 if pct >= 99.0:
603 raise ExternalError(msg)
604 elif pct >= 95.0:
605 print
606 print " WARNING: ", msg
607 print
608 elif OPTIONS.verbose:
609 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700610
611
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800612def ReadApkCerts(tf_zip):
613 """Given a target_files ZipFile, parse the META/apkcerts.txt file
614 and return a {package: cert} dict."""
615 certmap = {}
616 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
617 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700618 if not line:
619 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800620 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
621 r'private_key="(.*)"$', line)
622 if m:
623 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 public_key_suffix_len = len(OPTIONS.public_key_suffix)
625 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800626 if cert in SPECIAL_CERT_STRINGS and not privkey:
627 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700628 elif (cert.endswith(OPTIONS.public_key_suffix) and
629 privkey.endswith(OPTIONS.private_key_suffix) and
630 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
631 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800632 else:
633 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
634 return certmap
635
636
Doug Zongkereef39442009-04-02 12:14:19 -0700637COMMON_DOCSTRING = """
638 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700639 Prepend <dir>/bin to the list of places to search for binaries
640 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700641
Doug Zongker05d3dea2009-06-22 11:32:31 -0700642 -s (--device_specific) <file>
643 Path to the python module containing device-specific
644 releasetools code.
645
Doug Zongker8bec09e2009-11-30 15:37:14 -0800646 -x (--extra) <key=value>
647 Add a key/value pair to the 'extras' dict, which device-specific
648 extension code may look at.
649
Doug Zongkereef39442009-04-02 12:14:19 -0700650 -v (--verbose)
651 Show command lines being executed.
652
653 -h (--help)
654 Display this usage message and exit.
655"""
656
657def Usage(docstring):
658 print docstring.rstrip("\n")
659 print COMMON_DOCSTRING
660
661
662def ParseOptions(argv,
663 docstring,
664 extra_opts="", extra_long_opts=(),
665 extra_option_handler=None):
666 """Parse the options in argv and return any arguments that aren't
667 flags. docstring is the calling module's docstring, to be displayed
668 for errors and -h. extra_opts and extra_long_opts are for flags
669 defined by the caller, which are processed by passing them to
670 extra_option_handler."""
671
672 try:
673 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800674 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700675 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700676 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700677 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
678 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800679 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700681 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700682 Usage(docstring)
683 print "**", str(err), "**"
684 sys.exit(2)
685
Doug Zongkereef39442009-04-02 12:14:19 -0700686 for o, a in opts:
687 if o in ("-h", "--help"):
688 Usage(docstring)
689 sys.exit()
690 elif o in ("-v", "--verbose"):
691 OPTIONS.verbose = True
692 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700693 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700694 elif o in ("--signapk_path",):
695 OPTIONS.signapk_path = a
696 elif o in ("--extra_signapk_args",):
697 OPTIONS.extra_signapk_args = shlex.split(a)
698 elif o in ("--java_path",):
699 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700700 elif o in ("--java_args",):
701 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700702 elif o in ("--public_key_suffix",):
703 OPTIONS.public_key_suffix = a
704 elif o in ("--private_key_suffix",):
705 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800706 elif o in ("--boot_signer_path",):
707 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700708 elif o in ("--boot_signer_args",):
709 OPTIONS.boot_signer_args = shlex.split(a)
710 elif o in ("--verity_signer_path",):
711 OPTIONS.verity_signer_path = a
712 elif o in ("--verity_signer_args",):
713 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700714 elif o in ("-s", "--device_specific"):
715 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800716 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800717 key, value = a.split("=", 1)
718 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700719 else:
720 if extra_option_handler is None or not extra_option_handler(o, a):
721 assert False, "unknown option \"%s\"" % (o,)
722
Doug Zongker85448772014-09-09 14:59:20 -0700723 if OPTIONS.search_path:
724 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
725 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700726
727 return args
728
729
Doug Zongkerfc44a512014-08-26 13:10:25 -0700730def MakeTempFile(prefix=None, suffix=None):
731 """Make a temp file and add it to the list of things to be deleted
732 when Cleanup() is called. Return the filename."""
733 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
734 os.close(fd)
735 OPTIONS.tempfiles.append(fn)
736 return fn
737
738
Doug Zongkereef39442009-04-02 12:14:19 -0700739def Cleanup():
740 for i in OPTIONS.tempfiles:
741 if os.path.isdir(i):
742 shutil.rmtree(i)
743 else:
744 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700745
746
747class PasswordManager(object):
748 def __init__(self):
749 self.editor = os.getenv("EDITOR", None)
750 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
751
752 def GetPasswords(self, items):
753 """Get passwords corresponding to each string in 'items',
754 returning a dict. (The dict may have keys in addition to the
755 values in 'items'.)
756
757 Uses the passwords in $ANDROID_PW_FILE if available, letting the
758 user edit that file to add more needed passwords. If no editor is
759 available, or $ANDROID_PW_FILE isn't define, prompts the user
760 interactively in the ordinary way.
761 """
762
763 current = self.ReadFile()
764
765 first = True
766 while True:
767 missing = []
768 for i in items:
769 if i not in current or not current[i]:
770 missing.append(i)
771 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 if not missing:
773 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700774
775 for i in missing:
776 current[i] = ""
777
778 if not first:
779 print "key file %s still missing some passwords." % (self.pwfile,)
780 answer = raw_input("try to edit again? [y]> ").strip()
781 if answer and answer[0] not in 'yY':
782 raise RuntimeError("key passwords unavailable")
783 first = False
784
785 current = self.UpdateAndReadFile(current)
786
Dan Albert8b72aef2015-03-23 19:13:21 -0700787 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700788 """Prompt the user to enter a value (password) for each key in
789 'current' whose value is fales. Returns a new dict with all the
790 values.
791 """
792 result = {}
793 for k, v in sorted(current.iteritems()):
794 if v:
795 result[k] = v
796 else:
797 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700798 result[k] = getpass.getpass(
799 "Enter password for %s key> " % k).strip()
800 if result[k]:
801 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700802 return result
803
804 def UpdateAndReadFile(self, current):
805 if not self.editor or not self.pwfile:
806 return self.PromptResult(current)
807
808 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700809 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700810 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
811 f.write("# (Additional spaces are harmless.)\n\n")
812
813 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700814 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
815 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700816 f.write("[[[ %s ]]] %s\n" % (v, k))
817 if not v and first_line is None:
818 # position cursor on first line with no password.
819 first_line = i + 4
820 f.close()
821
822 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
823 _, _ = p.communicate()
824
825 return self.ReadFile()
826
827 def ReadFile(self):
828 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700829 if self.pwfile is None:
830 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700831 try:
832 f = open(self.pwfile, "r")
833 for line in f:
834 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700835 if not line or line[0] == '#':
836 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700837 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
838 if not m:
839 print "failed to parse password file: ", line
840 else:
841 result[m.group(2)] = m.group(1)
842 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700843 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700844 if e.errno != errno.ENOENT:
845 print "error reading password file: ", str(e)
846 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700847
848
Dan Albert8e0178d2015-01-27 15:53:15 -0800849def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
850 compress_type=None):
851 import datetime
852
853 # http://b/18015246
854 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
855 # for files larger than 2GiB. We can work around this by adjusting their
856 # limit. Note that `zipfile.writestr()` will not work for strings larger than
857 # 2GiB. The Python interpreter sometimes rejects strings that large (though
858 # it isn't clear to me exactly what circumstances cause this).
859 # `zipfile.write()` must be used directly to work around this.
860 #
861 # This mess can be avoided if we port to python3.
862 saved_zip64_limit = zipfile.ZIP64_LIMIT
863 zipfile.ZIP64_LIMIT = (1 << 32) - 1
864
865 if compress_type is None:
866 compress_type = zip_file.compression
867 if arcname is None:
868 arcname = filename
869
870 saved_stat = os.stat(filename)
871
872 try:
873 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
874 # file to be zipped and reset it when we're done.
875 os.chmod(filename, perms)
876
877 # Use a fixed timestamp so the output is repeatable.
878 epoch = datetime.datetime.fromtimestamp(0)
879 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
880 os.utime(filename, (timestamp, timestamp))
881
882 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
883 finally:
884 os.chmod(filename, saved_stat.st_mode)
885 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
886 zipfile.ZIP64_LIMIT = saved_zip64_limit
887
888
Tao Bao58c1b962015-05-20 09:32:18 -0700889def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700890 compress_type=None):
891 """Wrap zipfile.writestr() function to work around the zip64 limit.
892
893 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
894 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
895 when calling crc32(bytes).
896
897 But it still works fine to write a shorter string into a large zip file.
898 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
899 when we know the string won't be too long.
900 """
901
902 saved_zip64_limit = zipfile.ZIP64_LIMIT
903 zipfile.ZIP64_LIMIT = (1 << 32) - 1
904
905 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
906 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700907 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700908 if perms is None:
909 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800910 else:
Tao Baof3282b42015-04-01 11:21:55 -0700911 zinfo = zinfo_or_arcname
912
913 # If compress_type is given, it overrides the value in zinfo.
914 if compress_type is not None:
915 zinfo.compress_type = compress_type
916
Tao Bao58c1b962015-05-20 09:32:18 -0700917 # If perms is given, it has a priority.
918 if perms is not None:
919 zinfo.external_attr = perms << 16
920
Tao Baof3282b42015-04-01 11:21:55 -0700921 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700922 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
923
Dan Albert8b72aef2015-03-23 19:13:21 -0700924 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700925 zipfile.ZIP64_LIMIT = saved_zip64_limit
926
927
928def ZipClose(zip_file):
929 # http://b/18015246
930 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
931 # central directory.
932 saved_zip64_limit = zipfile.ZIP64_LIMIT
933 zipfile.ZIP64_LIMIT = (1 << 32) - 1
934
935 zip_file.close()
936
937 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700938
939
940class DeviceSpecificParams(object):
941 module = None
942 def __init__(self, **kwargs):
943 """Keyword arguments to the constructor become attributes of this
944 object, which is passed to all functions in the device-specific
945 module."""
946 for k, v in kwargs.iteritems():
947 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800948 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700949
950 if self.module is None:
951 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700952 if not path:
953 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700954 try:
955 if os.path.isdir(path):
956 info = imp.find_module("releasetools", [path])
957 else:
958 d, f = os.path.split(path)
959 b, x = os.path.splitext(f)
960 if x == ".py":
961 f = b
962 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800963 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700964 self.module = imp.load_module("device_specific", *info)
965 except ImportError:
966 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700967
968 def _DoCall(self, function_name, *args, **kwargs):
969 """Call the named function in the device-specific module, passing
970 the given args and kwargs. The first argument to the call will be
971 the DeviceSpecific object itself. If there is no module, or the
972 module does not define the function, return the value of the
973 'default' kwarg (which itself defaults to None)."""
974 if self.module is None or not hasattr(self.module, function_name):
975 return kwargs.get("default", None)
976 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
977
978 def FullOTA_Assertions(self):
979 """Called after emitting the block of assertions at the top of a
980 full OTA package. Implementations can add whatever additional
981 assertions they like."""
982 return self._DoCall("FullOTA_Assertions")
983
Doug Zongkere5ff5902012-01-17 10:55:37 -0800984 def FullOTA_InstallBegin(self):
985 """Called at the start of full OTA installation."""
986 return self._DoCall("FullOTA_InstallBegin")
987
Doug Zongker05d3dea2009-06-22 11:32:31 -0700988 def FullOTA_InstallEnd(self):
989 """Called at the end of full OTA installation; typically this is
990 used to install the image for the device's baseband processor."""
991 return self._DoCall("FullOTA_InstallEnd")
992
993 def IncrementalOTA_Assertions(self):
994 """Called after emitting the block of assertions at the top of an
995 incremental OTA package. Implementations can add whatever
996 additional assertions they like."""
997 return self._DoCall("IncrementalOTA_Assertions")
998
Doug Zongkere5ff5902012-01-17 10:55:37 -0800999 def IncrementalOTA_VerifyBegin(self):
1000 """Called at the start of the verification phase of incremental
1001 OTA installation; additional checks can be placed here to abort
1002 the script before any changes are made."""
1003 return self._DoCall("IncrementalOTA_VerifyBegin")
1004
Doug Zongker05d3dea2009-06-22 11:32:31 -07001005 def IncrementalOTA_VerifyEnd(self):
1006 """Called at the end of the verification phase of incremental OTA
1007 installation; additional checks can be placed here to abort the
1008 script before any changes are made."""
1009 return self._DoCall("IncrementalOTA_VerifyEnd")
1010
Doug Zongkere5ff5902012-01-17 10:55:37 -08001011 def IncrementalOTA_InstallBegin(self):
1012 """Called at the start of incremental OTA installation (after
1013 verification is complete)."""
1014 return self._DoCall("IncrementalOTA_InstallBegin")
1015
Doug Zongker05d3dea2009-06-22 11:32:31 -07001016 def IncrementalOTA_InstallEnd(self):
1017 """Called at the end of incremental OTA installation; typically
1018 this is used to install the image for the device's baseband
1019 processor."""
1020 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001021
1022class File(object):
1023 def __init__(self, name, data):
1024 self.name = name
1025 self.data = data
1026 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001027 self.sha1 = sha1(data).hexdigest()
1028
1029 @classmethod
1030 def FromLocalFile(cls, name, diskname):
1031 f = open(diskname, "rb")
1032 data = f.read()
1033 f.close()
1034 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001035
1036 def WriteToTemp(self):
1037 t = tempfile.NamedTemporaryFile()
1038 t.write(self.data)
1039 t.flush()
1040 return t
1041
Geremy Condra36bd3652014-02-06 19:45:10 -08001042 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001043 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001044
1045DIFF_PROGRAM_BY_EXT = {
1046 ".gz" : "imgdiff",
1047 ".zip" : ["imgdiff", "-z"],
1048 ".jar" : ["imgdiff", "-z"],
1049 ".apk" : ["imgdiff", "-z"],
1050 ".img" : "imgdiff",
1051 }
1052
1053class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001054 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001055 self.tf = tf
1056 self.sf = sf
1057 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001058 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001059
1060 def ComputePatch(self):
1061 """Compute the patch (as a string of data) needed to turn sf into
1062 tf. Returns the same tuple as GetPatch()."""
1063
1064 tf = self.tf
1065 sf = self.sf
1066
Doug Zongker24cd2802012-08-14 16:36:15 -07001067 if self.diff_program:
1068 diff_program = self.diff_program
1069 else:
1070 ext = os.path.splitext(tf.name)[1]
1071 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001072
1073 ttemp = tf.WriteToTemp()
1074 stemp = sf.WriteToTemp()
1075
1076 ext = os.path.splitext(tf.name)[1]
1077
1078 try:
1079 ptemp = tempfile.NamedTemporaryFile()
1080 if isinstance(diff_program, list):
1081 cmd = copy.copy(diff_program)
1082 else:
1083 cmd = [diff_program]
1084 cmd.append(stemp.name)
1085 cmd.append(ttemp.name)
1086 cmd.append(ptemp.name)
1087 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001088 err = []
1089 def run():
1090 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001091 if e:
1092 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001093 th = threading.Thread(target=run)
1094 th.start()
1095 th.join(timeout=300) # 5 mins
1096 if th.is_alive():
1097 print "WARNING: diff command timed out"
1098 p.terminate()
1099 th.join(5)
1100 if th.is_alive():
1101 p.kill()
1102 th.join()
1103
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001104 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001105 print "WARNING: failure running %s:\n%s\n" % (
1106 diff_program, "".join(err))
1107 self.patch = None
1108 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001109 diff = ptemp.read()
1110 finally:
1111 ptemp.close()
1112 stemp.close()
1113 ttemp.close()
1114
1115 self.patch = diff
1116 return self.tf, self.sf, self.patch
1117
1118
1119 def GetPatch(self):
1120 """Return a tuple (target_file, source_file, patch_data).
1121 patch_data may be None if ComputePatch hasn't been called, or if
1122 computing the patch failed."""
1123 return self.tf, self.sf, self.patch
1124
1125
1126def ComputeDifferences(diffs):
1127 """Call ComputePatch on all the Difference objects in 'diffs'."""
1128 print len(diffs), "diffs to compute"
1129
1130 # Do the largest files first, to try and reduce the long-pole effect.
1131 by_size = [(i.tf.size, i) for i in diffs]
1132 by_size.sort(reverse=True)
1133 by_size = [i[1] for i in by_size]
1134
1135 lock = threading.Lock()
1136 diff_iter = iter(by_size) # accessed under lock
1137
1138 def worker():
1139 try:
1140 lock.acquire()
1141 for d in diff_iter:
1142 lock.release()
1143 start = time.time()
1144 d.ComputePatch()
1145 dur = time.time() - start
1146 lock.acquire()
1147
1148 tf, sf, patch = d.GetPatch()
1149 if sf.name == tf.name:
1150 name = tf.name
1151 else:
1152 name = "%s (%s)" % (tf.name, sf.name)
1153 if patch is None:
1154 print "patching failed! %s" % (name,)
1155 else:
1156 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1157 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1158 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001159 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001160 print e
1161 raise
1162
1163 # start worker threads; wait for them all to finish.
1164 threads = [threading.Thread(target=worker)
1165 for i in range(OPTIONS.worker_threads)]
1166 for th in threads:
1167 th.start()
1168 while threads:
1169 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001170
1171
Dan Albert8b72aef2015-03-23 19:13:21 -07001172class BlockDifference(object):
1173 def __init__(self, partition, tgt, src=None, check_first_block=False,
1174 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001175 self.tgt = tgt
1176 self.src = src
1177 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001178 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001179
Tao Bao5ece99d2015-05-12 11:42:31 -07001180 # Due to http://b/20939131, check_first_block is disabled temporarily.
1181 assert not self.check_first_block
1182
Tao Baodd2a5892015-03-12 12:32:37 -07001183 if version is None:
1184 version = 1
1185 if OPTIONS.info_dict:
1186 version = max(
1187 int(i) for i in
1188 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1189 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001190
1191 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001192 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001193 tmpdir = tempfile.mkdtemp()
1194 OPTIONS.tempfiles.append(tmpdir)
1195 self.path = os.path.join(tmpdir, partition)
1196 b.Compute(self.path)
1197
1198 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1199
1200 def WriteScript(self, script, output_zip, progress=None):
1201 if not self.src:
1202 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001203 script.Print("Patching %s image unconditionally..." % (self.partition,))
1204 else:
1205 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001206
Dan Albert8b72aef2015-03-23 19:13:21 -07001207 if progress:
1208 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001209 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001210 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001211
1212 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001213 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001214 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001215 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001216 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001217 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1218 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001219 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001220 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1221 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001222 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001223 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001224 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001225 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001226 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001227 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001228 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001229 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001230 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001231
Tao Baodd2a5892015-03-12 12:32:37 -07001232 # When generating incrementals for the system and vendor partitions,
1233 # explicitly check the first block (which contains the superblock) of
1234 # the partition to see if it's what we expect. If this check fails,
1235 # give an explicit log message about the partition having been
1236 # remounted R/W (the most likely explanation) and the need to flash to
1237 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001238 if self.check_first_block:
1239 self._CheckFirstBlock(script)
1240
Tao Baodd2a5892015-03-12 12:32:37 -07001241 # Abort the OTA update. Note that the incremental OTA cannot be applied
1242 # even if it may match the checksum of the target partition.
1243 # a) If version < 3, operations like move and erase will make changes
1244 # unconditionally and damage the partition.
1245 # b) If version >= 3, it won't even reach here.
1246 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1247 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001248
Tao Bao5fcaaef2015-06-01 13:40:49 -07001249 def _WritePostInstallVerifyScript(self, script):
1250 partition = self.partition
1251 script.Print('Verifying the updated %s image...' % (partition,))
1252 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1253 ranges = self.tgt.care_map
1254 ranges_str = ranges.to_string_raw()
1255 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1256 self.device, ranges_str,
1257 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001258
1259 # Bug: 20881595
1260 # Verify that extended blocks are really zeroed out.
1261 if self.tgt.extended:
1262 ranges_str = self.tgt.extended.to_string_raw()
1263 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1264 self.device, ranges_str,
1265 self._HashZeroBlocks(self.tgt.extended.size())))
1266 script.Print('Verified the updated %s image.' % (partition,))
1267 script.AppendExtra(
1268 'else\n'
1269 ' abort("%s partition has unexpected non-zero contents after OTA '
1270 'update");\n'
1271 'endif;' % (partition,))
1272 else:
1273 script.Print('Verified the updated %s image.' % (partition,))
1274
Tao Bao5fcaaef2015-06-01 13:40:49 -07001275 script.AppendExtra(
1276 'else\n'
1277 ' abort("%s partition has unexpected contents after OTA update");\n'
1278 'endif;' % (partition,))
1279
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001280 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001281 ZipWrite(output_zip,
1282 '{}.transfer.list'.format(self.path),
1283 '{}.transfer.list'.format(self.partition))
1284 ZipWrite(output_zip,
1285 '{}.new.dat'.format(self.path),
1286 '{}.new.dat'.format(self.partition))
1287 ZipWrite(output_zip,
1288 '{}.patch.dat'.format(self.path),
1289 '{}.patch.dat'.format(self.partition),
1290 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001291
Dan Albert8e0178d2015-01-27 15:53:15 -08001292 call = ('block_image_update("{device}", '
1293 'package_extract_file("{partition}.transfer.list"), '
1294 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1295 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001296 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001297
Dan Albert8b72aef2015-03-23 19:13:21 -07001298 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001299 data = source.ReadRangeSet(ranges)
1300 ctx = sha1()
1301
1302 for p in data:
1303 ctx.update(p)
1304
1305 return ctx.hexdigest()
1306
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001307 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1308 """Return the hash value for all zero blocks."""
1309 zero_block = '\x00' * 4096
1310 ctx = sha1()
1311 for _ in range(num_blocks):
1312 ctx.update(zero_block)
1313
1314 return ctx.hexdigest()
1315
Tao Bao5ece99d2015-05-12 11:42:31 -07001316 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1317 # remounting R/W. Will change the checking to a finer-grained way to
1318 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001319 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001320 r = rangelib.RangeSet((0, 1))
1321 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001322
1323 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1324 'abort("%s has been remounted R/W; '
1325 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001326 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001327 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001328
1329DataImage = blockimgdiff.DataImage
1330
1331
Doug Zongker96a57e72010-09-26 14:57:41 -07001332# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001333PARTITION_TYPES = {
1334 "yaffs2": "MTD",
1335 "mtd": "MTD",
1336 "ext4": "EMMC",
1337 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001338 "f2fs": "EMMC",
1339 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001340}
Doug Zongker96a57e72010-09-26 14:57:41 -07001341
1342def GetTypeAndDevice(mount_point, info):
1343 fstab = info["fstab"]
1344 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001345 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1346 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001347 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001349
1350
1351def ParseCertificate(data):
1352 """Parse a PEM-format certificate."""
1353 cert = []
1354 save = False
1355 for line in data.split("\n"):
1356 if "--END CERTIFICATE--" in line:
1357 break
1358 if save:
1359 cert.append(line)
1360 if "--BEGIN CERTIFICATE--" in line:
1361 save = True
1362 cert = "".join(cert).decode('base64')
1363 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001364
Doug Zongker412c02f2014-02-13 10:58:24 -08001365def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1366 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001367 """Generate a binary patch that creates the recovery image starting
1368 with the boot image. (Most of the space in these images is just the
1369 kernel, which is identical for the two, so the resulting patch
1370 should be efficient.) Add it to the output zip, along with a shell
1371 script that is run from init.rc on first boot to actually do the
1372 patching and install the new recovery image.
1373
1374 recovery_img and boot_img should be File objects for the
1375 corresponding images. info should be the dictionary returned by
1376 common.LoadInfoDict() on the input target_files.
1377 """
1378
Doug Zongker412c02f2014-02-13 10:58:24 -08001379 if info_dict is None:
1380 info_dict = OPTIONS.info_dict
1381
Doug Zongkerc9253822014-02-04 12:17:58 -08001382 diff_program = ["imgdiff"]
1383 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1384 if os.path.exists(path):
1385 diff_program.append("-b")
1386 diff_program.append(path)
1387 bonus_args = "-b /system/etc/recovery-resource.dat"
1388 else:
1389 bonus_args = ""
1390
1391 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1392 _, _, patch = d.ComputePatch()
1393 output_sink("recovery-from-boot.p", patch)
1394
Dan Albertebb19aa2015-03-27 19:11:53 -07001395 try:
1396 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1397 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1398 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001399 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001400
1401 sh = """#!/system/bin/sh
1402if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1403 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"
1404else
1405 log -t recovery "Recovery image already installed"
1406fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001407""" % {'boot_size': boot_img.size,
1408 'boot_sha1': boot_img.sha1,
1409 'recovery_size': recovery_img.size,
1410 'recovery_sha1': recovery_img.sha1,
1411 'boot_type': boot_type,
1412 'boot_device': boot_device,
1413 'recovery_type': recovery_type,
1414 'recovery_device': recovery_device,
1415 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001416
1417 # The install script location moved from /system/etc to /system/bin
1418 # in the L release. Parse the init.rc file to find out where the
1419 # target-files expects it to be, and put it there.
1420 sh_location = "etc/install-recovery.sh"
1421 try:
1422 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1423 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001424 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001425 if m:
1426 sh_location = m.group(1)
1427 print "putting script in", sh_location
1428 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001429 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001430 print "failed to read init.rc: %s" % (e,)
1431
1432 output_sink(sh_location, sh)