blob: 2965fa80453a4943cdfe3cd82bc2d034257c288c [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",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700395 info_dict["vboot_key"] + ".vbprivk",
396 info_dict["vboot_subkey"] + ".vbprivk",
397 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700398 img.name]
399 p = Run(cmd, stdout=subprocess.PIPE)
400 p.communicate()
401 assert p.returncode == 0, "vboot_signer of %s image failed" % path
402
Tao Baof3282b42015-04-01 11:21:55 -0700403 # Clean up the temp files.
404 img_unsigned.close()
405 img_keyblock.close()
406
Doug Zongkereef39442009-04-02 12:14:19 -0700407 img.seek(os.SEEK_SET, 0)
408 data = img.read()
409
410 ramdisk_img.close()
411 img.close()
412
413 return data
414
415
Doug Zongkerd5131602012-08-02 14:46:42 -0700416def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
417 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800418 """Return a File object (with name 'name') with the desired bootable
419 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700420 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
421 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800422 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700423
Doug Zongker55d93282011-01-25 17:03:34 -0800424 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
425 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700426 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800427 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700428
429 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
430 if os.path.exists(prebuilt_path):
431 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
432 return File.FromLocalFile(name, prebuilt_path)
433
434 print "building image from target_files %s..." % (tree_subdir,)
435 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
436 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
437 os.path.join(unpack_dir, fs_config),
438 info_dict)
439 if data:
440 return File(name, data)
441 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800442
Doug Zongkereef39442009-04-02 12:14:19 -0700443
Doug Zongker75f17362009-12-08 13:46:44 -0800444def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800445 """Unzip the given archive into a temporary directory and return the name.
446
447 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
448 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
449
450 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
451 main file), open for reading.
452 """
Doug Zongkereef39442009-04-02 12:14:19 -0700453
454 tmp = tempfile.mkdtemp(prefix="targetfiles-")
455 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800456
457 def unzip_to_dir(filename, dirname):
458 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
459 if pattern is not None:
460 cmd.append(pattern)
461 p = Run(cmd, stdout=subprocess.PIPE)
462 p.communicate()
463 if p.returncode != 0:
464 raise ExternalError("failed to unzip input target-files \"%s\"" %
465 (filename,))
466
467 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
468 if m:
469 unzip_to_dir(m.group(1), tmp)
470 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
471 filename = m.group(1)
472 else:
473 unzip_to_dir(filename, tmp)
474
475 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700476
477
478def GetKeyPasswords(keylist):
479 """Given a list of keys, prompt the user to enter passwords for
480 those which require them. Return a {key: password} dict. password
481 will be None if the key has no password."""
482
Doug Zongker8ce7c252009-05-22 13:34:54 -0700483 no_passwords = []
484 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700485 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700486 devnull = open("/dev/null", "w+b")
487 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800488 # We don't need a password for things that aren't really keys.
489 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700490 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700491 continue
492
T.R. Fullhart37e10522013-03-18 10:31:26 -0700493 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700494 "-inform", "DER", "-nocrypt"],
495 stdin=devnull.fileno(),
496 stdout=devnull.fileno(),
497 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700498 p.communicate()
499 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700500 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700501 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700502 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700503 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
504 "-inform", "DER", "-passin", "pass:"],
505 stdin=devnull.fileno(),
506 stdout=devnull.fileno(),
507 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700508 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700509 if p.returncode == 0:
510 # Encrypted key with empty string as password.
511 key_passwords[k] = ''
512 elif stderr.startswith('Error decrypting key'):
513 # Definitely encrypted key.
514 # It would have said "Error reading key" if it didn't parse correctly.
515 need_passwords.append(k)
516 else:
517 # Potentially, a type of key that openssl doesn't understand.
518 # We'll let the routines in signapk.jar handle it.
519 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700520 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700521
T.R. Fullhart37e10522013-03-18 10:31:26 -0700522 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700523 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700524 return key_passwords
525
526
Doug Zongker951495f2009-08-14 12:44:19 -0700527def SignFile(input_name, output_name, key, password, align=None,
528 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700529 """Sign the input_name zip/jar/apk, producing output_name. Use the
530 given key and password (the latter may be None if the key does not
531 have a password.
532
533 If align is an integer > 1, zipalign is run to align stored files in
534 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700535
536 If whole_file is true, use the "-w" option to SignApk to embed a
537 signature that covers the whole file in the archive comment of the
538 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700539 """
Doug Zongker951495f2009-08-14 12:44:19 -0700540
Doug Zongkereef39442009-04-02 12:14:19 -0700541 if align == 0 or align == 1:
542 align = None
543
544 if align:
545 temp = tempfile.NamedTemporaryFile()
546 sign_name = temp.name
547 else:
548 sign_name = output_name
549
Baligh Uddin339ee492014-09-05 11:18:07 -0700550 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700551 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
552 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700553 if whole_file:
554 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700555 cmd.extend([key + OPTIONS.public_key_suffix,
556 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700557 input_name, sign_name])
558
559 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700560 if password is not None:
561 password += "\n"
562 p.communicate(password)
563 if p.returncode != 0:
564 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
565
566 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700567 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700568 p.communicate()
569 if p.returncode != 0:
570 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
571 temp.close()
572
573
Doug Zongker37974732010-09-16 17:44:38 -0700574def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700575 """Check the data string passed against the max size limit, if
576 any, for the given target. Raise exception if the data is too big.
577 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700578
Dan Albert8b72aef2015-03-23 19:13:21 -0700579 if target.endswith(".img"):
580 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700581 mount_point = "/" + target
582
Ying Wangf8824af2014-06-03 14:07:27 -0700583 fs_type = None
584 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700585 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700586 if mount_point == "/userdata":
587 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700588 p = info_dict["fstab"][mount_point]
589 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800590 device = p.device
591 if "/" in device:
592 device = device[device.rfind("/")+1:]
593 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700594 if not fs_type or not limit:
595 return
Doug Zongkereef39442009-04-02 12:14:19 -0700596
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700597 if fs_type == "yaffs2":
598 # image size should be increased by 1/64th to account for the
599 # spare area (64 bytes per 2k page)
600 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800601 size = len(data)
602 pct = float(size) * 100.0 / limit
603 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
604 if pct >= 99.0:
605 raise ExternalError(msg)
606 elif pct >= 95.0:
607 print
608 print " WARNING: ", msg
609 print
610 elif OPTIONS.verbose:
611 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700612
613
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800614def ReadApkCerts(tf_zip):
615 """Given a target_files ZipFile, parse the META/apkcerts.txt file
616 and return a {package: cert} dict."""
617 certmap = {}
618 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
619 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700620 if not line:
621 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800622 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
623 r'private_key="(.*)"$', line)
624 if m:
625 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700626 public_key_suffix_len = len(OPTIONS.public_key_suffix)
627 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800628 if cert in SPECIAL_CERT_STRINGS and not privkey:
629 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700630 elif (cert.endswith(OPTIONS.public_key_suffix) and
631 privkey.endswith(OPTIONS.private_key_suffix) and
632 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
633 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800634 else:
635 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
636 return certmap
637
638
Doug Zongkereef39442009-04-02 12:14:19 -0700639COMMON_DOCSTRING = """
640 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700641 Prepend <dir>/bin to the list of places to search for binaries
642 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700643
Doug Zongker05d3dea2009-06-22 11:32:31 -0700644 -s (--device_specific) <file>
645 Path to the python module containing device-specific
646 releasetools code.
647
Doug Zongker8bec09e2009-11-30 15:37:14 -0800648 -x (--extra) <key=value>
649 Add a key/value pair to the 'extras' dict, which device-specific
650 extension code may look at.
651
Doug Zongkereef39442009-04-02 12:14:19 -0700652 -v (--verbose)
653 Show command lines being executed.
654
655 -h (--help)
656 Display this usage message and exit.
657"""
658
659def Usage(docstring):
660 print docstring.rstrip("\n")
661 print COMMON_DOCSTRING
662
663
664def ParseOptions(argv,
665 docstring,
666 extra_opts="", extra_long_opts=(),
667 extra_option_handler=None):
668 """Parse the options in argv and return any arguments that aren't
669 flags. docstring is the calling module's docstring, to be displayed
670 for errors and -h. extra_opts and extra_long_opts are for flags
671 defined by the caller, which are processed by passing them to
672 extra_option_handler."""
673
674 try:
675 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800676 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700677 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700678 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700679 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
680 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800681 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700682 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700683 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700684 Usage(docstring)
685 print "**", str(err), "**"
686 sys.exit(2)
687
Doug Zongkereef39442009-04-02 12:14:19 -0700688 for o, a in opts:
689 if o in ("-h", "--help"):
690 Usage(docstring)
691 sys.exit()
692 elif o in ("-v", "--verbose"):
693 OPTIONS.verbose = True
694 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700695 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700696 elif o in ("--signapk_path",):
697 OPTIONS.signapk_path = a
698 elif o in ("--extra_signapk_args",):
699 OPTIONS.extra_signapk_args = shlex.split(a)
700 elif o in ("--java_path",):
701 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700702 elif o in ("--java_args",):
703 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700704 elif o in ("--public_key_suffix",):
705 OPTIONS.public_key_suffix = a
706 elif o in ("--private_key_suffix",):
707 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800708 elif o in ("--boot_signer_path",):
709 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700710 elif o in ("--boot_signer_args",):
711 OPTIONS.boot_signer_args = shlex.split(a)
712 elif o in ("--verity_signer_path",):
713 OPTIONS.verity_signer_path = a
714 elif o in ("--verity_signer_args",):
715 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700716 elif o in ("-s", "--device_specific"):
717 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800718 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800719 key, value = a.split("=", 1)
720 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700721 else:
722 if extra_option_handler is None or not extra_option_handler(o, a):
723 assert False, "unknown option \"%s\"" % (o,)
724
Doug Zongker85448772014-09-09 14:59:20 -0700725 if OPTIONS.search_path:
726 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
727 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700728
729 return args
730
731
Doug Zongkerfc44a512014-08-26 13:10:25 -0700732def MakeTempFile(prefix=None, suffix=None):
733 """Make a temp file and add it to the list of things to be deleted
734 when Cleanup() is called. Return the filename."""
735 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
736 os.close(fd)
737 OPTIONS.tempfiles.append(fn)
738 return fn
739
740
Doug Zongkereef39442009-04-02 12:14:19 -0700741def Cleanup():
742 for i in OPTIONS.tempfiles:
743 if os.path.isdir(i):
744 shutil.rmtree(i)
745 else:
746 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700747
748
749class PasswordManager(object):
750 def __init__(self):
751 self.editor = os.getenv("EDITOR", None)
752 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
753
754 def GetPasswords(self, items):
755 """Get passwords corresponding to each string in 'items',
756 returning a dict. (The dict may have keys in addition to the
757 values in 'items'.)
758
759 Uses the passwords in $ANDROID_PW_FILE if available, letting the
760 user edit that file to add more needed passwords. If no editor is
761 available, or $ANDROID_PW_FILE isn't define, prompts the user
762 interactively in the ordinary way.
763 """
764
765 current = self.ReadFile()
766
767 first = True
768 while True:
769 missing = []
770 for i in items:
771 if i not in current or not current[i]:
772 missing.append(i)
773 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700774 if not missing:
775 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776
777 for i in missing:
778 current[i] = ""
779
780 if not first:
781 print "key file %s still missing some passwords." % (self.pwfile,)
782 answer = raw_input("try to edit again? [y]> ").strip()
783 if answer and answer[0] not in 'yY':
784 raise RuntimeError("key passwords unavailable")
785 first = False
786
787 current = self.UpdateAndReadFile(current)
788
Dan Albert8b72aef2015-03-23 19:13:21 -0700789 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790 """Prompt the user to enter a value (password) for each key in
791 'current' whose value is fales. Returns a new dict with all the
792 values.
793 """
794 result = {}
795 for k, v in sorted(current.iteritems()):
796 if v:
797 result[k] = v
798 else:
799 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700800 result[k] = getpass.getpass(
801 "Enter password for %s key> " % k).strip()
802 if result[k]:
803 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700804 return result
805
806 def UpdateAndReadFile(self, current):
807 if not self.editor or not self.pwfile:
808 return self.PromptResult(current)
809
810 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700812 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
813 f.write("# (Additional spaces are harmless.)\n\n")
814
815 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700816 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
817 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700818 f.write("[[[ %s ]]] %s\n" % (v, k))
819 if not v and first_line is None:
820 # position cursor on first line with no password.
821 first_line = i + 4
822 f.close()
823
824 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
825 _, _ = p.communicate()
826
827 return self.ReadFile()
828
829 def ReadFile(self):
830 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700831 if self.pwfile is None:
832 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 try:
834 f = open(self.pwfile, "r")
835 for line in f:
836 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 if not line or line[0] == '#':
838 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
840 if not m:
841 print "failed to parse password file: ", line
842 else:
843 result[m.group(2)] = m.group(1)
844 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700845 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700846 if e.errno != errno.ENOENT:
847 print "error reading password file: ", str(e)
848 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700849
850
Dan Albert8e0178d2015-01-27 15:53:15 -0800851def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
852 compress_type=None):
853 import datetime
854
855 # http://b/18015246
856 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
857 # for files larger than 2GiB. We can work around this by adjusting their
858 # limit. Note that `zipfile.writestr()` will not work for strings larger than
859 # 2GiB. The Python interpreter sometimes rejects strings that large (though
860 # it isn't clear to me exactly what circumstances cause this).
861 # `zipfile.write()` must be used directly to work around this.
862 #
863 # This mess can be avoided if we port to python3.
864 saved_zip64_limit = zipfile.ZIP64_LIMIT
865 zipfile.ZIP64_LIMIT = (1 << 32) - 1
866
867 if compress_type is None:
868 compress_type = zip_file.compression
869 if arcname is None:
870 arcname = filename
871
872 saved_stat = os.stat(filename)
873
874 try:
875 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
876 # file to be zipped and reset it when we're done.
877 os.chmod(filename, perms)
878
879 # Use a fixed timestamp so the output is repeatable.
880 epoch = datetime.datetime.fromtimestamp(0)
881 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
882 os.utime(filename, (timestamp, timestamp))
883
884 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
885 finally:
886 os.chmod(filename, saved_stat.st_mode)
887 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
888 zipfile.ZIP64_LIMIT = saved_zip64_limit
889
890
Tao Bao58c1b962015-05-20 09:32:18 -0700891def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700892 compress_type=None):
893 """Wrap zipfile.writestr() function to work around the zip64 limit.
894
895 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
896 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
897 when calling crc32(bytes).
898
899 But it still works fine to write a shorter string into a large zip file.
900 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
901 when we know the string won't be too long.
902 """
903
904 saved_zip64_limit = zipfile.ZIP64_LIMIT
905 zipfile.ZIP64_LIMIT = (1 << 32) - 1
906
907 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
908 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700910 if perms is None:
911 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800912 else:
Tao Baof3282b42015-04-01 11:21:55 -0700913 zinfo = zinfo_or_arcname
914
915 # If compress_type is given, it overrides the value in zinfo.
916 if compress_type is not None:
917 zinfo.compress_type = compress_type
918
Tao Bao58c1b962015-05-20 09:32:18 -0700919 # If perms is given, it has a priority.
920 if perms is not None:
921 zinfo.external_attr = perms << 16
922
Tao Baof3282b42015-04-01 11:21:55 -0700923 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700924 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
925
Dan Albert8b72aef2015-03-23 19:13:21 -0700926 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700927 zipfile.ZIP64_LIMIT = saved_zip64_limit
928
929
930def ZipClose(zip_file):
931 # http://b/18015246
932 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
933 # central directory.
934 saved_zip64_limit = zipfile.ZIP64_LIMIT
935 zipfile.ZIP64_LIMIT = (1 << 32) - 1
936
937 zip_file.close()
938
939 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700940
941
942class DeviceSpecificParams(object):
943 module = None
944 def __init__(self, **kwargs):
945 """Keyword arguments to the constructor become attributes of this
946 object, which is passed to all functions in the device-specific
947 module."""
948 for k, v in kwargs.iteritems():
949 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800950 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700951
952 if self.module is None:
953 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700954 if not path:
955 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700956 try:
957 if os.path.isdir(path):
958 info = imp.find_module("releasetools", [path])
959 else:
960 d, f = os.path.split(path)
961 b, x = os.path.splitext(f)
962 if x == ".py":
963 f = b
964 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800965 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700966 self.module = imp.load_module("device_specific", *info)
967 except ImportError:
968 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700969
970 def _DoCall(self, function_name, *args, **kwargs):
971 """Call the named function in the device-specific module, passing
972 the given args and kwargs. The first argument to the call will be
973 the DeviceSpecific object itself. If there is no module, or the
974 module does not define the function, return the value of the
975 'default' kwarg (which itself defaults to None)."""
976 if self.module is None or not hasattr(self.module, function_name):
977 return kwargs.get("default", None)
978 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
979
980 def FullOTA_Assertions(self):
981 """Called after emitting the block of assertions at the top of a
982 full OTA package. Implementations can add whatever additional
983 assertions they like."""
984 return self._DoCall("FullOTA_Assertions")
985
Doug Zongkere5ff5902012-01-17 10:55:37 -0800986 def FullOTA_InstallBegin(self):
987 """Called at the start of full OTA installation."""
988 return self._DoCall("FullOTA_InstallBegin")
989
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990 def FullOTA_InstallEnd(self):
991 """Called at the end of full OTA installation; typically this is
992 used to install the image for the device's baseband processor."""
993 return self._DoCall("FullOTA_InstallEnd")
994
995 def IncrementalOTA_Assertions(self):
996 """Called after emitting the block of assertions at the top of an
997 incremental OTA package. Implementations can add whatever
998 additional assertions they like."""
999 return self._DoCall("IncrementalOTA_Assertions")
1000
Doug Zongkere5ff5902012-01-17 10:55:37 -08001001 def IncrementalOTA_VerifyBegin(self):
1002 """Called at the start of the verification phase of incremental
1003 OTA installation; additional checks can be placed here to abort
1004 the script before any changes are made."""
1005 return self._DoCall("IncrementalOTA_VerifyBegin")
1006
Doug Zongker05d3dea2009-06-22 11:32:31 -07001007 def IncrementalOTA_VerifyEnd(self):
1008 """Called at the end of the verification phase of incremental OTA
1009 installation; additional checks can be placed here to abort the
1010 script before any changes are made."""
1011 return self._DoCall("IncrementalOTA_VerifyEnd")
1012
Doug Zongkere5ff5902012-01-17 10:55:37 -08001013 def IncrementalOTA_InstallBegin(self):
1014 """Called at the start of incremental OTA installation (after
1015 verification is complete)."""
1016 return self._DoCall("IncrementalOTA_InstallBegin")
1017
Doug Zongker05d3dea2009-06-22 11:32:31 -07001018 def IncrementalOTA_InstallEnd(self):
1019 """Called at the end of incremental OTA installation; typically
1020 this is used to install the image for the device's baseband
1021 processor."""
1022 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001023
1024class File(object):
1025 def __init__(self, name, data):
1026 self.name = name
1027 self.data = data
1028 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001029 self.sha1 = sha1(data).hexdigest()
1030
1031 @classmethod
1032 def FromLocalFile(cls, name, diskname):
1033 f = open(diskname, "rb")
1034 data = f.read()
1035 f.close()
1036 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001037
1038 def WriteToTemp(self):
1039 t = tempfile.NamedTemporaryFile()
1040 t.write(self.data)
1041 t.flush()
1042 return t
1043
Geremy Condra36bd3652014-02-06 19:45:10 -08001044 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001045 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001046
1047DIFF_PROGRAM_BY_EXT = {
1048 ".gz" : "imgdiff",
1049 ".zip" : ["imgdiff", "-z"],
1050 ".jar" : ["imgdiff", "-z"],
1051 ".apk" : ["imgdiff", "-z"],
1052 ".img" : "imgdiff",
1053 }
1054
1055class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001056 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001057 self.tf = tf
1058 self.sf = sf
1059 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001060 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001061
1062 def ComputePatch(self):
1063 """Compute the patch (as a string of data) needed to turn sf into
1064 tf. Returns the same tuple as GetPatch()."""
1065
1066 tf = self.tf
1067 sf = self.sf
1068
Doug Zongker24cd2802012-08-14 16:36:15 -07001069 if self.diff_program:
1070 diff_program = self.diff_program
1071 else:
1072 ext = os.path.splitext(tf.name)[1]
1073 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001074
1075 ttemp = tf.WriteToTemp()
1076 stemp = sf.WriteToTemp()
1077
1078 ext = os.path.splitext(tf.name)[1]
1079
1080 try:
1081 ptemp = tempfile.NamedTemporaryFile()
1082 if isinstance(diff_program, list):
1083 cmd = copy.copy(diff_program)
1084 else:
1085 cmd = [diff_program]
1086 cmd.append(stemp.name)
1087 cmd.append(ttemp.name)
1088 cmd.append(ptemp.name)
1089 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001090 err = []
1091 def run():
1092 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001093 if e:
1094 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001095 th = threading.Thread(target=run)
1096 th.start()
1097 th.join(timeout=300) # 5 mins
1098 if th.is_alive():
1099 print "WARNING: diff command timed out"
1100 p.terminate()
1101 th.join(5)
1102 if th.is_alive():
1103 p.kill()
1104 th.join()
1105
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001106 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001107 print "WARNING: failure running %s:\n%s\n" % (
1108 diff_program, "".join(err))
1109 self.patch = None
1110 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001111 diff = ptemp.read()
1112 finally:
1113 ptemp.close()
1114 stemp.close()
1115 ttemp.close()
1116
1117 self.patch = diff
1118 return self.tf, self.sf, self.patch
1119
1120
1121 def GetPatch(self):
1122 """Return a tuple (target_file, source_file, patch_data).
1123 patch_data may be None if ComputePatch hasn't been called, or if
1124 computing the patch failed."""
1125 return self.tf, self.sf, self.patch
1126
1127
1128def ComputeDifferences(diffs):
1129 """Call ComputePatch on all the Difference objects in 'diffs'."""
1130 print len(diffs), "diffs to compute"
1131
1132 # Do the largest files first, to try and reduce the long-pole effect.
1133 by_size = [(i.tf.size, i) for i in diffs]
1134 by_size.sort(reverse=True)
1135 by_size = [i[1] for i in by_size]
1136
1137 lock = threading.Lock()
1138 diff_iter = iter(by_size) # accessed under lock
1139
1140 def worker():
1141 try:
1142 lock.acquire()
1143 for d in diff_iter:
1144 lock.release()
1145 start = time.time()
1146 d.ComputePatch()
1147 dur = time.time() - start
1148 lock.acquire()
1149
1150 tf, sf, patch = d.GetPatch()
1151 if sf.name == tf.name:
1152 name = tf.name
1153 else:
1154 name = "%s (%s)" % (tf.name, sf.name)
1155 if patch is None:
1156 print "patching failed! %s" % (name,)
1157 else:
1158 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1159 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1160 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001161 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001162 print e
1163 raise
1164
1165 # start worker threads; wait for them all to finish.
1166 threads = [threading.Thread(target=worker)
1167 for i in range(OPTIONS.worker_threads)]
1168 for th in threads:
1169 th.start()
1170 while threads:
1171 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001172
1173
Dan Albert8b72aef2015-03-23 19:13:21 -07001174class BlockDifference(object):
1175 def __init__(self, partition, tgt, src=None, check_first_block=False,
1176 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001177 self.tgt = tgt
1178 self.src = src
1179 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001180 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001181
Tao Bao5ece99d2015-05-12 11:42:31 -07001182 # Due to http://b/20939131, check_first_block is disabled temporarily.
1183 assert not self.check_first_block
1184
Tao Baodd2a5892015-03-12 12:32:37 -07001185 if version is None:
1186 version = 1
1187 if OPTIONS.info_dict:
1188 version = max(
1189 int(i) for i in
1190 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1191 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001192
1193 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001194 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001195 tmpdir = tempfile.mkdtemp()
1196 OPTIONS.tempfiles.append(tmpdir)
1197 self.path = os.path.join(tmpdir, partition)
1198 b.Compute(self.path)
1199
Tao Baoe09359a2015-10-13 16:37:12 -07001200 if src is None:
1201 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1202 else:
1203 _, self.device = GetTypeAndDevice("/" + partition,
1204 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001205
1206 def WriteScript(self, script, output_zip, progress=None):
1207 if not self.src:
1208 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001209 script.Print("Patching %s image unconditionally..." % (self.partition,))
1210 else:
1211 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001212
Dan Albert8b72aef2015-03-23 19:13:21 -07001213 if progress:
1214 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001215 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001216 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001217
1218 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001219 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001220 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001221 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001222 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001223 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1224 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001225 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001226 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1227 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001228 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001229 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001230 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001231 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001232 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001233 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001234 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001235 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001236 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001237
Tao Baodd2a5892015-03-12 12:32:37 -07001238 # When generating incrementals for the system and vendor partitions,
1239 # explicitly check the first block (which contains the superblock) of
1240 # the partition to see if it's what we expect. If this check fails,
1241 # give an explicit log message about the partition having been
1242 # remounted R/W (the most likely explanation) and the need to flash to
1243 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001244 if self.check_first_block:
1245 self._CheckFirstBlock(script)
1246
Tao Baodd2a5892015-03-12 12:32:37 -07001247 # Abort the OTA update. Note that the incremental OTA cannot be applied
1248 # even if it may match the checksum of the target partition.
1249 # a) If version < 3, operations like move and erase will make changes
1250 # unconditionally and damage the partition.
1251 # b) If version >= 3, it won't even reach here.
1252 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1253 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254
Tao Bao5fcaaef2015-06-01 13:40:49 -07001255 def _WritePostInstallVerifyScript(self, script):
1256 partition = self.partition
1257 script.Print('Verifying the updated %s image...' % (partition,))
1258 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1259 ranges = self.tgt.care_map
1260 ranges_str = ranges.to_string_raw()
1261 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1262 self.device, ranges_str,
1263 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001264
1265 # Bug: 20881595
1266 # Verify that extended blocks are really zeroed out.
1267 if self.tgt.extended:
1268 ranges_str = self.tgt.extended.to_string_raw()
1269 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1270 self.device, ranges_str,
1271 self._HashZeroBlocks(self.tgt.extended.size())))
1272 script.Print('Verified the updated %s image.' % (partition,))
1273 script.AppendExtra(
1274 'else\n'
1275 ' abort("%s partition has unexpected non-zero contents after OTA '
1276 'update");\n'
1277 'endif;' % (partition,))
1278 else:
1279 script.Print('Verified the updated %s image.' % (partition,))
1280
Tao Bao5fcaaef2015-06-01 13:40:49 -07001281 script.AppendExtra(
1282 'else\n'
1283 ' abort("%s partition has unexpected contents after OTA update");\n'
1284 'endif;' % (partition,))
1285
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001286 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001287 ZipWrite(output_zip,
1288 '{}.transfer.list'.format(self.path),
1289 '{}.transfer.list'.format(self.partition))
1290 ZipWrite(output_zip,
1291 '{}.new.dat'.format(self.path),
1292 '{}.new.dat'.format(self.partition))
1293 ZipWrite(output_zip,
1294 '{}.patch.dat'.format(self.path),
1295 '{}.patch.dat'.format(self.partition),
1296 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001297
Dan Albert8e0178d2015-01-27 15:53:15 -08001298 call = ('block_image_update("{device}", '
1299 'package_extract_file("{partition}.transfer.list"), '
1300 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1301 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001302 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001303
Dan Albert8b72aef2015-03-23 19:13:21 -07001304 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001305 data = source.ReadRangeSet(ranges)
1306 ctx = sha1()
1307
1308 for p in data:
1309 ctx.update(p)
1310
1311 return ctx.hexdigest()
1312
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001313 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1314 """Return the hash value for all zero blocks."""
1315 zero_block = '\x00' * 4096
1316 ctx = sha1()
1317 for _ in range(num_blocks):
1318 ctx.update(zero_block)
1319
1320 return ctx.hexdigest()
1321
Tao Bao5ece99d2015-05-12 11:42:31 -07001322 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1323 # remounting R/W. Will change the checking to a finer-grained way to
1324 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001325 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001326 r = rangelib.RangeSet((0, 1))
1327 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001328
1329 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1330 'abort("%s has been remounted R/W; '
1331 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001332 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001333 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001334
1335DataImage = blockimgdiff.DataImage
1336
1337
Doug Zongker96a57e72010-09-26 14:57:41 -07001338# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001339PARTITION_TYPES = {
1340 "yaffs2": "MTD",
1341 "mtd": "MTD",
1342 "ext4": "EMMC",
1343 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001344 "f2fs": "EMMC",
1345 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001346}
Doug Zongker96a57e72010-09-26 14:57:41 -07001347
1348def GetTypeAndDevice(mount_point, info):
1349 fstab = info["fstab"]
1350 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001351 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1352 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001353 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001354 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001355
1356
1357def ParseCertificate(data):
1358 """Parse a PEM-format certificate."""
1359 cert = []
1360 save = False
1361 for line in data.split("\n"):
1362 if "--END CERTIFICATE--" in line:
1363 break
1364 if save:
1365 cert.append(line)
1366 if "--BEGIN CERTIFICATE--" in line:
1367 save = True
1368 cert = "".join(cert).decode('base64')
1369 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001370
Doug Zongker412c02f2014-02-13 10:58:24 -08001371def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1372 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001373 """Generate a binary patch that creates the recovery image starting
1374 with the boot image. (Most of the space in these images is just the
1375 kernel, which is identical for the two, so the resulting patch
1376 should be efficient.) Add it to the output zip, along with a shell
1377 script that is run from init.rc on first boot to actually do the
1378 patching and install the new recovery image.
1379
1380 recovery_img and boot_img should be File objects for the
1381 corresponding images. info should be the dictionary returned by
1382 common.LoadInfoDict() on the input target_files.
1383 """
1384
Doug Zongker412c02f2014-02-13 10:58:24 -08001385 if info_dict is None:
1386 info_dict = OPTIONS.info_dict
1387
Doug Zongkerc9253822014-02-04 12:17:58 -08001388 diff_program = ["imgdiff"]
1389 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1390 if os.path.exists(path):
1391 diff_program.append("-b")
1392 diff_program.append(path)
1393 bonus_args = "-b /system/etc/recovery-resource.dat"
1394 else:
1395 bonus_args = ""
1396
1397 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1398 _, _, patch = d.ComputePatch()
1399 output_sink("recovery-from-boot.p", patch)
1400
Dan Albertebb19aa2015-03-27 19:11:53 -07001401 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001402 # The following GetTypeAndDevice()s need to use the path in the target
1403 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001404 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1405 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1406 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001407 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001408
1409 sh = """#!/system/bin/sh
1410if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1411 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"
1412else
1413 log -t recovery "Recovery image already installed"
1414fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001415""" % {'boot_size': boot_img.size,
1416 'boot_sha1': boot_img.sha1,
1417 'recovery_size': recovery_img.size,
1418 'recovery_sha1': recovery_img.sha1,
1419 'boot_type': boot_type,
1420 'boot_device': boot_device,
1421 'recovery_type': recovery_type,
1422 'recovery_device': recovery_device,
1423 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001424
1425 # The install script location moved from /system/etc to /system/bin
Tao Bao610754e2015-07-07 18:31:47 -07001426 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001427 # target-files expects it to be, and put it there.
1428 sh_location = "etc/install-recovery.sh"
Tao Bao610754e2015-07-07 18:31:47 -07001429 found = False
1430 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
1431 init_rc_files = os.listdir(init_rc_dir)
1432 for init_rc_file in init_rc_files:
1433 if (not init_rc_file.startswith('init.') or
1434 not init_rc_file.endswith('.rc')):
1435 continue
1436
1437 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001438 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001439 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001440 if m:
1441 sh_location = m.group(1)
Tao Bao610754e2015-07-07 18:31:47 -07001442 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001443 break
Tao Bao610754e2015-07-07 18:31:47 -07001444
1445 if found:
1446 break
1447
1448 print "putting script in", sh_location
Doug Zongkerc9253822014-02-04 12:17:58 -08001449
1450 output_sink(sh_location, sh)