blob: 27b8f274bca09d5bbcc69b17669e129c8ecba147 [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
Tao Baoe09359a2015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
65
66
67OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070068
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080069
70# Values for "certificate" in apkcerts that mean special things.
71SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
72
73
Dan Albert8b72aef2015-03-23 19:13:21 -070074class ExternalError(RuntimeError):
75 pass
Doug Zongkereef39442009-04-02 12:14:19 -070076
77
78def Run(args, **kwargs):
79 """Create and return a subprocess.Popen object, printing the command
80 line on the terminal if -v was specified."""
81 if OPTIONS.verbose:
82 print " running: ", " ".join(args)
83 return subprocess.Popen(args, **kwargs)
84
85
Ying Wang7e6d4e42010-12-13 16:25:36 -080086def CloseInheritedPipes():
87 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
88 before doing other work."""
89 if platform.system() != "Darwin":
90 return
91 for d in range(3, 1025):
92 try:
93 stat = os.fstat(d)
94 if stat is not None:
95 pipebit = stat[0] & 0x1000
96 if pipebit != 0:
97 os.close(d)
98 except OSError:
99 pass
100
101
Dan Albert8b72aef2015-03-23 19:13:21 -0700102def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700103 """Read and parse the META/misc_info.txt key/value pairs from the
104 input target files and return a dict."""
105
Doug Zongkerc9253822014-02-04 12:17:58 -0800106 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700107 if isinstance(input_file, zipfile.ZipFile):
108 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700110 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800111 try:
112 with open(path) as f:
113 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700114 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800115 if e.errno == errno.ENOENT:
116 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700117 d = {}
118 try:
Michael Runge6e836112014-04-15 17:40:21 -0700119 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700120 except KeyError:
121 # ok if misc_info.txt doesn't exist
122 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700123
Doug Zongker37974732010-09-16 17:44:38 -0700124 # backwards compatibility: These values used to be in their own
125 # files. Look for them, in case we're processing an old
126 # target_files zip.
127
128 if "mkyaffs2_extra_flags" not in d:
129 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700130 d["mkyaffs2_extra_flags"] = read_helper(
131 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700132 except KeyError:
133 # ok if flags don't exist
134 pass
135
136 if "recovery_api_version" not in d:
137 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700138 d["recovery_api_version"] = read_helper(
139 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700140 except KeyError:
141 raise ValueError("can't find recovery API version in input target-files")
142
143 if "tool_extensions" not in d:
144 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800145 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700146 except KeyError:
147 # ok if extensions don't exist
148 pass
149
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800150 if "fstab_version" not in d:
151 d["fstab_version"] = "1"
152
Doug Zongker37974732010-09-16 17:44:38 -0700153 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800154 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700155 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 if not line:
157 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700158 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 if not value:
160 continue
Doug Zongker37974732010-09-16 17:44:38 -0700161 if name == "blocksize":
162 d[name] = value
163 else:
164 d[name + "_size"] = value
165 except KeyError:
166 pass
167
168 def makeint(key):
169 if key in d:
170 d[key] = int(d[key], 0)
171
172 makeint("recovery_api_version")
173 makeint("blocksize")
174 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700175 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700177 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700178 makeint("recovery_size")
179 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800180 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700181
Doug Zongkerc9253822014-02-04 12:17:58 -0800182 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
183 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184 return d
185
Doug Zongkerc9253822014-02-04 12:17:58 -0800186def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800188 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700189 except KeyError:
190 print "Warning: could not find SYSTEM/build.prop in %s" % zip
191 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700192 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193
Michael Runge6e836112014-04-15 17:40:21 -0700194def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700196 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700197 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700198 if not line or line.startswith("#"):
199 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700200 if "=" in line:
201 name, value = line.split("=", 1)
202 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700203 return d
204
Doug Zongkerc9253822014-02-04 12:17:58 -0800205def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700206 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700207 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700208 self.mount_point = mount_point
209 self.fs_type = fs_type
210 self.device = device
211 self.length = length
212 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700213 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214
215 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700217 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800218 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700219 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700220
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800221 if fstab_version == 1:
222 d = {}
223 for line in data.split("\n"):
224 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not line or line.startswith("#"):
226 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800229 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 options = None
231 if len(pieces) >= 4:
232 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700233 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800234 if len(pieces) >= 5:
235 options = pieces[4]
236 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800239 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700241
Dan Albert8b72aef2015-03-23 19:13:21 -0700242 mount_point = pieces[0]
243 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 if options:
245 options = options.split(",")
246 for i in options:
247 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800251
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
253 device=pieces[2], length=length,
254 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255
256 elif fstab_version == 2:
257 d = {}
258 for line in data.split("\n"):
259 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 if not line or line.startswith("#"):
261 continue
Tao Bao548eb762015-06-10 12:32:41 -0700262 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 pieces = line.split()
264 if len(pieces) != 5:
265 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
266
267 # Ignore entries that are managed by vold
268 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 if "voldmanaged=" in options:
270 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271
272 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800274 options = options.split(",")
275 for i in options:
276 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800278 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279 # Ignore all unknown options in the unified fstab
280 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800281
Tao Bao548eb762015-06-10 12:32:41 -0700282 mount_flags = pieces[3]
283 # Honor the SELinux context if present.
284 context = None
285 for i in mount_flags.split(","):
286 if i.startswith("context="):
287 context = i
288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 mount_point = pieces[1]
290 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700291 device=pieces[0], length=length,
292 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800293
294 else:
295 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
296
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700297 return d
298
299
Doug Zongker37974732010-09-16 17:44:38 -0700300def DumpInfoDict(d):
301 for k, v in sorted(d.items()):
302 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700303
Dan Albert8b72aef2015-03-23 19:13:21 -0700304
Doug Zongkerd5131602012-08-02 14:46:42 -0700305def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700306 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700307 'sourcedir'), and turn them into a boot image. Return the image
308 data, or None if sourcedir does not appear to contains files for
309 building the requested image."""
310
311 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
312 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
313 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700314
Doug Zongkerd5131602012-08-02 14:46:42 -0700315 if info_dict is None:
316 info_dict = OPTIONS.info_dict
317
Doug Zongkereef39442009-04-02 12:14:19 -0700318 ramdisk_img = tempfile.NamedTemporaryFile()
319 img = tempfile.NamedTemporaryFile()
320
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700321 if os.access(fs_config_file, os.F_OK):
322 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
323 else:
324 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
325 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700326 p2 = Run(["minigzip"],
327 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700328
329 p2.wait()
330 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700331 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
332 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700333
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800334 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
335 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
336
337 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700338
Benoit Fradina45a8682014-07-14 21:00:43 +0200339 fn = os.path.join(sourcedir, "second")
340 if os.access(fn, os.F_OK):
341 cmd.append("--second")
342 cmd.append(fn)
343
Doug Zongker171f1cd2009-06-15 22:36:37 -0700344 fn = os.path.join(sourcedir, "cmdline")
345 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700346 cmd.append("--cmdline")
347 cmd.append(open(fn).read().rstrip("\n"))
348
349 fn = os.path.join(sourcedir, "base")
350 if os.access(fn, os.F_OK):
351 cmd.append("--base")
352 cmd.append(open(fn).read().rstrip("\n"))
353
Ying Wang4de6b5b2010-08-25 14:29:34 -0700354 fn = os.path.join(sourcedir, "pagesize")
355 if os.access(fn, os.F_OK):
356 cmd.append("--pagesize")
357 cmd.append(open(fn).read().rstrip("\n"))
358
Doug Zongkerd5131602012-08-02 14:46:42 -0700359 args = info_dict.get("mkbootimg_args", None)
360 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700361 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700362
Tao Baod95e9fd2015-03-29 23:07:41 -0700363 img_unsigned = None
364 if info_dict.get("vboot", None):
365 img_unsigned = tempfile.NamedTemporaryFile()
366 cmd.extend(["--ramdisk", ramdisk_img.name,
367 "--output", img_unsigned.name])
368 else:
369 cmd.extend(["--ramdisk", ramdisk_img.name,
370 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700371
372 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700373 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700374 assert p.returncode == 0, "mkbootimg of %s image failed" % (
375 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700376
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100377 if (info_dict.get("boot_signer", None) == "true" and
378 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700379 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700380 cmd = [OPTIONS.boot_signer_path]
381 cmd.extend(OPTIONS.boot_signer_args)
382 cmd.extend([path, img.name,
383 info_dict["verity_key"] + ".pk8",
384 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700385 p = Run(cmd, stdout=subprocess.PIPE)
386 p.communicate()
387 assert p.returncode == 0, "boot_signer of %s image failed" % path
388
Tao Baod95e9fd2015-03-29 23:07:41 -0700389 # Sign the image if vboot is non-empty.
390 elif info_dict.get("vboot", None):
391 path = "/" + os.path.basename(sourcedir).lower()
392 img_keyblock = tempfile.NamedTemporaryFile()
393 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
394 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
395 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
396 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
Tao Baoe09359a2015-10-13 16:37:12 -07001198 if src is None:
1199 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1200 else:
1201 _, self.device = GetTypeAndDevice("/" + partition,
1202 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001203
1204 def WriteScript(self, script, output_zip, progress=None):
1205 if not self.src:
1206 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001207 script.Print("Patching %s image unconditionally..." % (self.partition,))
1208 else:
1209 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001210
Dan Albert8b72aef2015-03-23 19:13:21 -07001211 if progress:
1212 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001213 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001214 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001215
1216 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001217 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001218 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001219 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001220 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001221 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1222 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001223 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001224 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1225 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001226 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001227 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001228 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001229 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001230 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001231 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001232 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001233 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001234 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001235
Tao Baodd2a5892015-03-12 12:32:37 -07001236 # When generating incrementals for the system and vendor partitions,
1237 # explicitly check the first block (which contains the superblock) of
1238 # the partition to see if it's what we expect. If this check fails,
1239 # give an explicit log message about the partition having been
1240 # remounted R/W (the most likely explanation) and the need to flash to
1241 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001242 if self.check_first_block:
1243 self._CheckFirstBlock(script)
1244
Tao Baodd2a5892015-03-12 12:32:37 -07001245 # Abort the OTA update. Note that the incremental OTA cannot be applied
1246 # even if it may match the checksum of the target partition.
1247 # a) If version < 3, operations like move and erase will make changes
1248 # unconditionally and damage the partition.
1249 # b) If version >= 3, it won't even reach here.
1250 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1251 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001252
Tao Bao5fcaaef2015-06-01 13:40:49 -07001253 def _WritePostInstallVerifyScript(self, script):
1254 partition = self.partition
1255 script.Print('Verifying the updated %s image...' % (partition,))
1256 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1257 ranges = self.tgt.care_map
1258 ranges_str = ranges.to_string_raw()
1259 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1260 self.device, ranges_str,
1261 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001262
1263 # Bug: 20881595
1264 # Verify that extended blocks are really zeroed out.
1265 if self.tgt.extended:
1266 ranges_str = self.tgt.extended.to_string_raw()
1267 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1268 self.device, ranges_str,
1269 self._HashZeroBlocks(self.tgt.extended.size())))
1270 script.Print('Verified the updated %s image.' % (partition,))
1271 script.AppendExtra(
1272 'else\n'
1273 ' abort("%s partition has unexpected non-zero contents after OTA '
1274 'update");\n'
1275 'endif;' % (partition,))
1276 else:
1277 script.Print('Verified the updated %s image.' % (partition,))
1278
Tao Bao5fcaaef2015-06-01 13:40:49 -07001279 script.AppendExtra(
1280 'else\n'
1281 ' abort("%s partition has unexpected contents after OTA update");\n'
1282 'endif;' % (partition,))
1283
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001284 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001285 ZipWrite(output_zip,
1286 '{}.transfer.list'.format(self.path),
1287 '{}.transfer.list'.format(self.partition))
1288 ZipWrite(output_zip,
1289 '{}.new.dat'.format(self.path),
1290 '{}.new.dat'.format(self.partition))
1291 ZipWrite(output_zip,
1292 '{}.patch.dat'.format(self.path),
1293 '{}.patch.dat'.format(self.partition),
1294 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001295
Dan Albert8e0178d2015-01-27 15:53:15 -08001296 call = ('block_image_update("{device}", '
1297 'package_extract_file("{partition}.transfer.list"), '
1298 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1299 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001300 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001301
Dan Albert8b72aef2015-03-23 19:13:21 -07001302 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001303 data = source.ReadRangeSet(ranges)
1304 ctx = sha1()
1305
1306 for p in data:
1307 ctx.update(p)
1308
1309 return ctx.hexdigest()
1310
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001311 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1312 """Return the hash value for all zero blocks."""
1313 zero_block = '\x00' * 4096
1314 ctx = sha1()
1315 for _ in range(num_blocks):
1316 ctx.update(zero_block)
1317
1318 return ctx.hexdigest()
1319
Tao Bao5ece99d2015-05-12 11:42:31 -07001320 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1321 # remounting R/W. Will change the checking to a finer-grained way to
1322 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001323 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001324 r = rangelib.RangeSet((0, 1))
1325 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001326
1327 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1328 'abort("%s has been remounted R/W; '
1329 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001330 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001331 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001332
1333DataImage = blockimgdiff.DataImage
1334
1335
Doug Zongker96a57e72010-09-26 14:57:41 -07001336# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001337PARTITION_TYPES = {
1338 "yaffs2": "MTD",
1339 "mtd": "MTD",
1340 "ext4": "EMMC",
1341 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001342 "f2fs": "EMMC",
1343 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001344}
Doug Zongker96a57e72010-09-26 14:57:41 -07001345
1346def GetTypeAndDevice(mount_point, info):
1347 fstab = info["fstab"]
1348 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001349 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1350 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001351 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001352 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001353
1354
1355def ParseCertificate(data):
1356 """Parse a PEM-format certificate."""
1357 cert = []
1358 save = False
1359 for line in data.split("\n"):
1360 if "--END CERTIFICATE--" in line:
1361 break
1362 if save:
1363 cert.append(line)
1364 if "--BEGIN CERTIFICATE--" in line:
1365 save = True
1366 cert = "".join(cert).decode('base64')
1367 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001368
Doug Zongker412c02f2014-02-13 10:58:24 -08001369def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1370 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001371 """Generate a binary patch that creates the recovery image starting
1372 with the boot image. (Most of the space in these images is just the
1373 kernel, which is identical for the two, so the resulting patch
1374 should be efficient.) Add it to the output zip, along with a shell
1375 script that is run from init.rc on first boot to actually do the
1376 patching and install the new recovery image.
1377
1378 recovery_img and boot_img should be File objects for the
1379 corresponding images. info should be the dictionary returned by
1380 common.LoadInfoDict() on the input target_files.
1381 """
1382
Doug Zongker412c02f2014-02-13 10:58:24 -08001383 if info_dict is None:
1384 info_dict = OPTIONS.info_dict
1385
Doug Zongkerc9253822014-02-04 12:17:58 -08001386 diff_program = ["imgdiff"]
1387 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1388 if os.path.exists(path):
1389 diff_program.append("-b")
1390 diff_program.append(path)
1391 bonus_args = "-b /system/etc/recovery-resource.dat"
1392 else:
1393 bonus_args = ""
1394
1395 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1396 _, _, patch = d.ComputePatch()
1397 output_sink("recovery-from-boot.p", patch)
1398
Dan Albertebb19aa2015-03-27 19:11:53 -07001399 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001400 # The following GetTypeAndDevice()s need to use the path in the target
1401 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001402 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1403 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1404 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001405 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001406
1407 sh = """#!/system/bin/sh
1408if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1409 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"
1410else
1411 log -t recovery "Recovery image already installed"
1412fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001413""" % {'boot_size': boot_img.size,
1414 'boot_sha1': boot_img.sha1,
1415 'recovery_size': recovery_img.size,
1416 'recovery_sha1': recovery_img.sha1,
1417 'boot_type': boot_type,
1418 'boot_device': boot_device,
1419 'recovery_type': recovery_type,
1420 'recovery_device': recovery_device,
1421 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001422
1423 # The install script location moved from /system/etc to /system/bin
1424 # in the L release. Parse the init.rc file to find out where the
1425 # target-files expects it to be, and put it there.
1426 sh_location = "etc/install-recovery.sh"
1427 try:
1428 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1429 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001430 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001431 if m:
1432 sh_location = m.group(1)
1433 print "putting script in", sh_location
1434 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001435 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001436 print "failed to read init.rc: %s" % (e,)
1437
1438 output_sink(sh_location, sh)