blob: 4b276c3e7020d6feccc2b751f18db7754ce78a36 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
62 self.worker_threads = None
63
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Dan Albert8b72aef2015-03-23 19:13:21 -0700100def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Doug Zongker37974732010-09-16 17:44:38 -0700151 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700153 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not line:
155 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700156 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if not value:
158 continue
Doug Zongker37974732010-09-16 17:44:38 -0700159 if name == "blocksize":
160 d[name] = value
161 else:
162 d[name + "_size"] = value
163 except KeyError:
164 pass
165
166 def makeint(key):
167 if key in d:
168 d[key] = int(d[key], 0)
169
170 makeint("recovery_api_version")
171 makeint("blocksize")
172 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700173 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700175 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("recovery_size")
177 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700179
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
181 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 except KeyError:
188 print "Warning: could not find SYSTEM/build.prop in %s" % zip
189 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700190 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191
Michael Runge6e836112014-04-15 17:40:21 -0700192def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700194 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 if not line or line.startswith("#"):
197 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700198 if "=" in line:
199 name, value = line.split("=", 1)
200 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700201 return d
202
Doug Zongkerc9253822014-02-04 12:17:58 -0800203def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204 class Partition(object):
Dan Albert8b72aef2015-03-23 19:13:21 -0700205 def __init__(self, mount_point, fs_type, device, length, device2):
206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700211
212 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800213 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800215 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700216 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700217
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800218 if fstab_version == 1:
219 d = {}
220 for line in data.split("\n"):
221 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700222 if not line or line.startswith("#"):
223 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800224 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800226 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 options = None
228 if len(pieces) >= 4:
229 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700230 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 if len(pieces) >= 5:
232 options = pieces[4]
233 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800235 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800236 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700238
Dan Albert8b72aef2015-03-23 19:13:21 -0700239 mount_point = pieces[0]
240 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800241 if options:
242 options = options.split(",")
243 for i in options:
244 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700245 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700247 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248
Dan Albert8b72aef2015-03-23 19:13:21 -0700249 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
250 device=pieces[2], length=length,
251 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800252
253 elif fstab_version == 2:
254 d = {}
255 for line in data.split("\n"):
256 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700257 if not line or line.startswith("#"):
258 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259 pieces = line.split()
260 if len(pieces) != 5:
261 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
262
263 # Ignore entries that are managed by vold
264 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700265 if "voldmanaged=" in options:
266 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267
268 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800270 options = options.split(",")
271 for i in options:
272 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800274 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275 # Ignore all unknown options in the unified fstab
276 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800277
Dan Albert8b72aef2015-03-23 19:13:21 -0700278 mount_point = pieces[1]
279 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
280 device=pieces[0], length=length, device2=None)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800281
282 else:
283 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
284
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700285 return d
286
287
Doug Zongker37974732010-09-16 17:44:38 -0700288def DumpInfoDict(d):
289 for k, v in sorted(d.items()):
290 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700291
Dan Albert8b72aef2015-03-23 19:13:21 -0700292
Doug Zongkerd5131602012-08-02 14:46:42 -0700293def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700294 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700295 'sourcedir'), and turn them into a boot image. Return the image
296 data, or None if sourcedir does not appear to contains files for
297 building the requested image."""
298
299 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
300 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
301 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303 if info_dict is None:
304 info_dict = OPTIONS.info_dict
305
Doug Zongkereef39442009-04-02 12:14:19 -0700306 ramdisk_img = tempfile.NamedTemporaryFile()
307 img = tempfile.NamedTemporaryFile()
308
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700309 if os.access(fs_config_file, os.F_OK):
310 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
311 else:
312 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
313 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700314 p2 = Run(["minigzip"],
315 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700316
317 p2.wait()
318 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
320 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700321
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800322 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
323 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
324
325 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700326
Benoit Fradina45a8682014-07-14 21:00:43 +0200327 fn = os.path.join(sourcedir, "second")
328 if os.access(fn, os.F_OK):
329 cmd.append("--second")
330 cmd.append(fn)
331
Doug Zongker171f1cd2009-06-15 22:36:37 -0700332 fn = os.path.join(sourcedir, "cmdline")
333 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700334 cmd.append("--cmdline")
335 cmd.append(open(fn).read().rstrip("\n"))
336
337 fn = os.path.join(sourcedir, "base")
338 if os.access(fn, os.F_OK):
339 cmd.append("--base")
340 cmd.append(open(fn).read().rstrip("\n"))
341
Ying Wang4de6b5b2010-08-25 14:29:34 -0700342 fn = os.path.join(sourcedir, "pagesize")
343 if os.access(fn, os.F_OK):
344 cmd.append("--pagesize")
345 cmd.append(open(fn).read().rstrip("\n"))
346
Doug Zongkerd5131602012-08-02 14:46:42 -0700347 args = info_dict.get("mkbootimg_args", None)
348 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700349 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700350
Tao Baod95e9fd2015-03-29 23:07:41 -0700351 img_unsigned = None
352 if info_dict.get("vboot", None):
353 img_unsigned = tempfile.NamedTemporaryFile()
354 cmd.extend(["--ramdisk", ramdisk_img.name,
355 "--output", img_unsigned.name])
356 else:
357 cmd.extend(["--ramdisk", ramdisk_img.name,
358 "--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700359
360 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700361 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700362 assert p.returncode == 0, "mkbootimg of %s image failed" % (
363 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700364
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100365 if (info_dict.get("boot_signer", None) == "true" and
366 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700367 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700368 cmd = [OPTIONS.boot_signer_path]
369 cmd.extend(OPTIONS.boot_signer_args)
370 cmd.extend([path, img.name,
371 info_dict["verity_key"] + ".pk8",
372 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700373 p = Run(cmd, stdout=subprocess.PIPE)
374 p.communicate()
375 assert p.returncode == 0, "boot_signer of %s image failed" % path
376
Tao Baod95e9fd2015-03-29 23:07:41 -0700377 # Sign the image if vboot is non-empty.
378 elif info_dict.get("vboot", None):
379 path = "/" + os.path.basename(sourcedir).lower()
380 img_keyblock = tempfile.NamedTemporaryFile()
381 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
382 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
383 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
384 img.name]
385 p = Run(cmd, stdout=subprocess.PIPE)
386 p.communicate()
387 assert p.returncode == 0, "vboot_signer of %s image failed" % path
388
Tao Baof3282b42015-04-01 11:21:55 -0700389 # Clean up the temp files.
390 img_unsigned.close()
391 img_keyblock.close()
392
Doug Zongkereef39442009-04-02 12:14:19 -0700393 img.seek(os.SEEK_SET, 0)
394 data = img.read()
395
396 ramdisk_img.close()
397 img.close()
398
399 return data
400
401
Doug Zongkerd5131602012-08-02 14:46:42 -0700402def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
403 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800404 """Return a File object (with name 'name') with the desired bootable
405 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700406 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
407 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800408 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700409
Doug Zongker55d93282011-01-25 17:03:34 -0800410 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
411 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700412 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800413 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700414
415 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
416 if os.path.exists(prebuilt_path):
417 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
418 return File.FromLocalFile(name, prebuilt_path)
419
420 print "building image from target_files %s..." % (tree_subdir,)
421 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
422 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
423 os.path.join(unpack_dir, fs_config),
424 info_dict)
425 if data:
426 return File(name, data)
427 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800428
Doug Zongkereef39442009-04-02 12:14:19 -0700429
Doug Zongker75f17362009-12-08 13:46:44 -0800430def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800431 """Unzip the given archive into a temporary directory and return the name.
432
433 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
434 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
435
436 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
437 main file), open for reading.
438 """
Doug Zongkereef39442009-04-02 12:14:19 -0700439
440 tmp = tempfile.mkdtemp(prefix="targetfiles-")
441 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800442
443 def unzip_to_dir(filename, dirname):
444 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
445 if pattern is not None:
446 cmd.append(pattern)
447 p = Run(cmd, stdout=subprocess.PIPE)
448 p.communicate()
449 if p.returncode != 0:
450 raise ExternalError("failed to unzip input target-files \"%s\"" %
451 (filename,))
452
453 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
454 if m:
455 unzip_to_dir(m.group(1), tmp)
456 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
457 filename = m.group(1)
458 else:
459 unzip_to_dir(filename, tmp)
460
461 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700462
463
464def GetKeyPasswords(keylist):
465 """Given a list of keys, prompt the user to enter passwords for
466 those which require them. Return a {key: password} dict. password
467 will be None if the key has no password."""
468
Doug Zongker8ce7c252009-05-22 13:34:54 -0700469 no_passwords = []
470 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700471 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700472 devnull = open("/dev/null", "w+b")
473 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800474 # We don't need a password for things that aren't really keys.
475 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700476 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700477 continue
478
T.R. Fullhart37e10522013-03-18 10:31:26 -0700479 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700480 "-inform", "DER", "-nocrypt"],
481 stdin=devnull.fileno(),
482 stdout=devnull.fileno(),
483 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700484 p.communicate()
485 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700486 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700487 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700488 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700489 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
490 "-inform", "DER", "-passin", "pass:"],
491 stdin=devnull.fileno(),
492 stdout=devnull.fileno(),
493 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700494 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700495 if p.returncode == 0:
496 # Encrypted key with empty string as password.
497 key_passwords[k] = ''
498 elif stderr.startswith('Error decrypting key'):
499 # Definitely encrypted key.
500 # It would have said "Error reading key" if it didn't parse correctly.
501 need_passwords.append(k)
502 else:
503 # Potentially, a type of key that openssl doesn't understand.
504 # We'll let the routines in signapk.jar handle it.
505 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700506 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700507
T.R. Fullhart37e10522013-03-18 10:31:26 -0700508 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700509 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700510 return key_passwords
511
512
Doug Zongker951495f2009-08-14 12:44:19 -0700513def SignFile(input_name, output_name, key, password, align=None,
514 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700515 """Sign the input_name zip/jar/apk, producing output_name. Use the
516 given key and password (the latter may be None if the key does not
517 have a password.
518
519 If align is an integer > 1, zipalign is run to align stored files in
520 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700521
522 If whole_file is true, use the "-w" option to SignApk to embed a
523 signature that covers the whole file in the archive comment of the
524 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700525 """
Doug Zongker951495f2009-08-14 12:44:19 -0700526
Doug Zongkereef39442009-04-02 12:14:19 -0700527 if align == 0 or align == 1:
528 align = None
529
530 if align:
531 temp = tempfile.NamedTemporaryFile()
532 sign_name = temp.name
533 else:
534 sign_name = output_name
535
Baligh Uddin339ee492014-09-05 11:18:07 -0700536 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700537 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
538 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700539 if whole_file:
540 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700541 cmd.extend([key + OPTIONS.public_key_suffix,
542 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700543 input_name, sign_name])
544
545 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700546 if password is not None:
547 password += "\n"
548 p.communicate(password)
549 if p.returncode != 0:
550 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
551
552 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700553 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700554 p.communicate()
555 if p.returncode != 0:
556 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
557 temp.close()
558
559
Doug Zongker37974732010-09-16 17:44:38 -0700560def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700561 """Check the data string passed against the max size limit, if
562 any, for the given target. Raise exception if the data is too big.
563 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700564
Dan Albert8b72aef2015-03-23 19:13:21 -0700565 if target.endswith(".img"):
566 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700567 mount_point = "/" + target
568
Ying Wangf8824af2014-06-03 14:07:27 -0700569 fs_type = None
570 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700571 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700572 if mount_point == "/userdata":
573 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700574 p = info_dict["fstab"][mount_point]
575 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800576 device = p.device
577 if "/" in device:
578 device = device[device.rfind("/")+1:]
579 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700580 if not fs_type or not limit:
581 return
Doug Zongkereef39442009-04-02 12:14:19 -0700582
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700583 if fs_type == "yaffs2":
584 # image size should be increased by 1/64th to account for the
585 # spare area (64 bytes per 2k page)
586 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800587 size = len(data)
588 pct = float(size) * 100.0 / limit
589 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
590 if pct >= 99.0:
591 raise ExternalError(msg)
592 elif pct >= 95.0:
593 print
594 print " WARNING: ", msg
595 print
596 elif OPTIONS.verbose:
597 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700598
599
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800600def ReadApkCerts(tf_zip):
601 """Given a target_files ZipFile, parse the META/apkcerts.txt file
602 and return a {package: cert} dict."""
603 certmap = {}
604 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
605 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700606 if not line:
607 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800608 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
609 r'private_key="(.*)"$', line)
610 if m:
611 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700612 public_key_suffix_len = len(OPTIONS.public_key_suffix)
613 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800614 if cert in SPECIAL_CERT_STRINGS and not privkey:
615 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700616 elif (cert.endswith(OPTIONS.public_key_suffix) and
617 privkey.endswith(OPTIONS.private_key_suffix) and
618 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
619 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800620 else:
621 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
622 return certmap
623
624
Doug Zongkereef39442009-04-02 12:14:19 -0700625COMMON_DOCSTRING = """
626 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700627 Prepend <dir>/bin to the list of places to search for binaries
628 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700629
Doug Zongker05d3dea2009-06-22 11:32:31 -0700630 -s (--device_specific) <file>
631 Path to the python module containing device-specific
632 releasetools code.
633
Doug Zongker8bec09e2009-11-30 15:37:14 -0800634 -x (--extra) <key=value>
635 Add a key/value pair to the 'extras' dict, which device-specific
636 extension code may look at.
637
Doug Zongkereef39442009-04-02 12:14:19 -0700638 -v (--verbose)
639 Show command lines being executed.
640
641 -h (--help)
642 Display this usage message and exit.
643"""
644
645def Usage(docstring):
646 print docstring.rstrip("\n")
647 print COMMON_DOCSTRING
648
649
650def ParseOptions(argv,
651 docstring,
652 extra_opts="", extra_long_opts=(),
653 extra_option_handler=None):
654 """Parse the options in argv and return any arguments that aren't
655 flags. docstring is the calling module's docstring, to be displayed
656 for errors and -h. extra_opts and extra_long_opts are for flags
657 defined by the caller, which are processed by passing them to
658 extra_option_handler."""
659
660 try:
661 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800662 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700663 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700664 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700665 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
666 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800667 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700668 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700669 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700670 Usage(docstring)
671 print "**", str(err), "**"
672 sys.exit(2)
673
Doug Zongkereef39442009-04-02 12:14:19 -0700674 for o, a in opts:
675 if o in ("-h", "--help"):
676 Usage(docstring)
677 sys.exit()
678 elif o in ("-v", "--verbose"):
679 OPTIONS.verbose = True
680 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700681 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700682 elif o in ("--signapk_path",):
683 OPTIONS.signapk_path = a
684 elif o in ("--extra_signapk_args",):
685 OPTIONS.extra_signapk_args = shlex.split(a)
686 elif o in ("--java_path",):
687 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700688 elif o in ("--java_args",):
689 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700690 elif o in ("--public_key_suffix",):
691 OPTIONS.public_key_suffix = a
692 elif o in ("--private_key_suffix",):
693 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800694 elif o in ("--boot_signer_path",):
695 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700696 elif o in ("--boot_signer_args",):
697 OPTIONS.boot_signer_args = shlex.split(a)
698 elif o in ("--verity_signer_path",):
699 OPTIONS.verity_signer_path = a
700 elif o in ("--verity_signer_args",):
701 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700702 elif o in ("-s", "--device_specific"):
703 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800704 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800705 key, value = a.split("=", 1)
706 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700707 else:
708 if extra_option_handler is None or not extra_option_handler(o, a):
709 assert False, "unknown option \"%s\"" % (o,)
710
Doug Zongker85448772014-09-09 14:59:20 -0700711 if OPTIONS.search_path:
712 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
713 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700714
715 return args
716
717
Doug Zongkerfc44a512014-08-26 13:10:25 -0700718def MakeTempFile(prefix=None, suffix=None):
719 """Make a temp file and add it to the list of things to be deleted
720 when Cleanup() is called. Return the filename."""
721 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
722 os.close(fd)
723 OPTIONS.tempfiles.append(fn)
724 return fn
725
726
Doug Zongkereef39442009-04-02 12:14:19 -0700727def Cleanup():
728 for i in OPTIONS.tempfiles:
729 if os.path.isdir(i):
730 shutil.rmtree(i)
731 else:
732 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700733
734
735class PasswordManager(object):
736 def __init__(self):
737 self.editor = os.getenv("EDITOR", None)
738 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
739
740 def GetPasswords(self, items):
741 """Get passwords corresponding to each string in 'items',
742 returning a dict. (The dict may have keys in addition to the
743 values in 'items'.)
744
745 Uses the passwords in $ANDROID_PW_FILE if available, letting the
746 user edit that file to add more needed passwords. If no editor is
747 available, or $ANDROID_PW_FILE isn't define, prompts the user
748 interactively in the ordinary way.
749 """
750
751 current = self.ReadFile()
752
753 first = True
754 while True:
755 missing = []
756 for i in items:
757 if i not in current or not current[i]:
758 missing.append(i)
759 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700760 if not missing:
761 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700762
763 for i in missing:
764 current[i] = ""
765
766 if not first:
767 print "key file %s still missing some passwords." % (self.pwfile,)
768 answer = raw_input("try to edit again? [y]> ").strip()
769 if answer and answer[0] not in 'yY':
770 raise RuntimeError("key passwords unavailable")
771 first = False
772
773 current = self.UpdateAndReadFile(current)
774
Dan Albert8b72aef2015-03-23 19:13:21 -0700775 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776 """Prompt the user to enter a value (password) for each key in
777 'current' whose value is fales. Returns a new dict with all the
778 values.
779 """
780 result = {}
781 for k, v in sorted(current.iteritems()):
782 if v:
783 result[k] = v
784 else:
785 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700786 result[k] = getpass.getpass(
787 "Enter password for %s key> " % k).strip()
788 if result[k]:
789 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700790 return result
791
792 def UpdateAndReadFile(self, current):
793 if not self.editor or not self.pwfile:
794 return self.PromptResult(current)
795
796 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700797 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700798 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
799 f.write("# (Additional spaces are harmless.)\n\n")
800
801 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700802 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
803 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700804 f.write("[[[ %s ]]] %s\n" % (v, k))
805 if not v and first_line is None:
806 # position cursor on first line with no password.
807 first_line = i + 4
808 f.close()
809
810 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
811 _, _ = p.communicate()
812
813 return self.ReadFile()
814
815 def ReadFile(self):
816 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700817 if self.pwfile is None:
818 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700819 try:
820 f = open(self.pwfile, "r")
821 for line in f:
822 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700823 if not line or line[0] == '#':
824 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700825 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
826 if not m:
827 print "failed to parse password file: ", line
828 else:
829 result[m.group(2)] = m.group(1)
830 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700831 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700832 if e.errno != errno.ENOENT:
833 print "error reading password file: ", str(e)
834 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700835
836
Dan Albert8e0178d2015-01-27 15:53:15 -0800837def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
838 compress_type=None):
839 import datetime
840
841 # http://b/18015246
842 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
843 # for files larger than 2GiB. We can work around this by adjusting their
844 # limit. Note that `zipfile.writestr()` will not work for strings larger than
845 # 2GiB. The Python interpreter sometimes rejects strings that large (though
846 # it isn't clear to me exactly what circumstances cause this).
847 # `zipfile.write()` must be used directly to work around this.
848 #
849 # This mess can be avoided if we port to python3.
850 saved_zip64_limit = zipfile.ZIP64_LIMIT
851 zipfile.ZIP64_LIMIT = (1 << 32) - 1
852
853 if compress_type is None:
854 compress_type = zip_file.compression
855 if arcname is None:
856 arcname = filename
857
858 saved_stat = os.stat(filename)
859
860 try:
861 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
862 # file to be zipped and reset it when we're done.
863 os.chmod(filename, perms)
864
865 # Use a fixed timestamp so the output is repeatable.
866 epoch = datetime.datetime.fromtimestamp(0)
867 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
868 os.utime(filename, (timestamp, timestamp))
869
870 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
871 finally:
872 os.chmod(filename, saved_stat.st_mode)
873 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
874 zipfile.ZIP64_LIMIT = saved_zip64_limit
875
876
Tao Bao58c1b962015-05-20 09:32:18 -0700877def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700878 compress_type=None):
879 """Wrap zipfile.writestr() function to work around the zip64 limit.
880
881 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
882 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
883 when calling crc32(bytes).
884
885 But it still works fine to write a shorter string into a large zip file.
886 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
887 when we know the string won't be too long.
888 """
889
890 saved_zip64_limit = zipfile.ZIP64_LIMIT
891 zipfile.ZIP64_LIMIT = (1 << 32) - 1
892
893 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
894 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700895 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700896 if perms is None:
897 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800898 else:
Tao Baof3282b42015-04-01 11:21:55 -0700899 zinfo = zinfo_or_arcname
900
901 # If compress_type is given, it overrides the value in zinfo.
902 if compress_type is not None:
903 zinfo.compress_type = compress_type
904
Tao Bao58c1b962015-05-20 09:32:18 -0700905 # If perms is given, it has a priority.
906 if perms is not None:
907 zinfo.external_attr = perms << 16
908
Tao Baof3282b42015-04-01 11:21:55 -0700909 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700910 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
911
Dan Albert8b72aef2015-03-23 19:13:21 -0700912 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700913 zipfile.ZIP64_LIMIT = saved_zip64_limit
914
915
916def ZipClose(zip_file):
917 # http://b/18015246
918 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
919 # central directory.
920 saved_zip64_limit = zipfile.ZIP64_LIMIT
921 zipfile.ZIP64_LIMIT = (1 << 32) - 1
922
923 zip_file.close()
924
925 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700926
927
928class DeviceSpecificParams(object):
929 module = None
930 def __init__(self, **kwargs):
931 """Keyword arguments to the constructor become attributes of this
932 object, which is passed to all functions in the device-specific
933 module."""
934 for k, v in kwargs.iteritems():
935 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800936 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700937
938 if self.module is None:
939 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700940 if not path:
941 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700942 try:
943 if os.path.isdir(path):
944 info = imp.find_module("releasetools", [path])
945 else:
946 d, f = os.path.split(path)
947 b, x = os.path.splitext(f)
948 if x == ".py":
949 f = b
950 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800951 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700952 self.module = imp.load_module("device_specific", *info)
953 except ImportError:
954 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700955
956 def _DoCall(self, function_name, *args, **kwargs):
957 """Call the named function in the device-specific module, passing
958 the given args and kwargs. The first argument to the call will be
959 the DeviceSpecific object itself. If there is no module, or the
960 module does not define the function, return the value of the
961 'default' kwarg (which itself defaults to None)."""
962 if self.module is None or not hasattr(self.module, function_name):
963 return kwargs.get("default", None)
964 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
965
966 def FullOTA_Assertions(self):
967 """Called after emitting the block of assertions at the top of a
968 full OTA package. Implementations can add whatever additional
969 assertions they like."""
970 return self._DoCall("FullOTA_Assertions")
971
Doug Zongkere5ff5902012-01-17 10:55:37 -0800972 def FullOTA_InstallBegin(self):
973 """Called at the start of full OTA installation."""
974 return self._DoCall("FullOTA_InstallBegin")
975
Doug Zongker05d3dea2009-06-22 11:32:31 -0700976 def FullOTA_InstallEnd(self):
977 """Called at the end of full OTA installation; typically this is
978 used to install the image for the device's baseband processor."""
979 return self._DoCall("FullOTA_InstallEnd")
980
981 def IncrementalOTA_Assertions(self):
982 """Called after emitting the block of assertions at the top of an
983 incremental OTA package. Implementations can add whatever
984 additional assertions they like."""
985 return self._DoCall("IncrementalOTA_Assertions")
986
Doug Zongkere5ff5902012-01-17 10:55:37 -0800987 def IncrementalOTA_VerifyBegin(self):
988 """Called at the start of the verification phase of incremental
989 OTA installation; additional checks can be placed here to abort
990 the script before any changes are made."""
991 return self._DoCall("IncrementalOTA_VerifyBegin")
992
Doug Zongker05d3dea2009-06-22 11:32:31 -0700993 def IncrementalOTA_VerifyEnd(self):
994 """Called at the end of the verification phase of incremental OTA
995 installation; additional checks can be placed here to abort the
996 script before any changes are made."""
997 return self._DoCall("IncrementalOTA_VerifyEnd")
998
Doug Zongkere5ff5902012-01-17 10:55:37 -0800999 def IncrementalOTA_InstallBegin(self):
1000 """Called at the start of incremental OTA installation (after
1001 verification is complete)."""
1002 return self._DoCall("IncrementalOTA_InstallBegin")
1003
Doug Zongker05d3dea2009-06-22 11:32:31 -07001004 def IncrementalOTA_InstallEnd(self):
1005 """Called at the end of incremental OTA installation; typically
1006 this is used to install the image for the device's baseband
1007 processor."""
1008 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001009
1010class File(object):
1011 def __init__(self, name, data):
1012 self.name = name
1013 self.data = data
1014 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001015 self.sha1 = sha1(data).hexdigest()
1016
1017 @classmethod
1018 def FromLocalFile(cls, name, diskname):
1019 f = open(diskname, "rb")
1020 data = f.read()
1021 f.close()
1022 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001023
1024 def WriteToTemp(self):
1025 t = tempfile.NamedTemporaryFile()
1026 t.write(self.data)
1027 t.flush()
1028 return t
1029
Geremy Condra36bd3652014-02-06 19:45:10 -08001030 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001031 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001032
1033DIFF_PROGRAM_BY_EXT = {
1034 ".gz" : "imgdiff",
1035 ".zip" : ["imgdiff", "-z"],
1036 ".jar" : ["imgdiff", "-z"],
1037 ".apk" : ["imgdiff", "-z"],
1038 ".img" : "imgdiff",
1039 }
1040
1041class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001042 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001043 self.tf = tf
1044 self.sf = sf
1045 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001046 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001047
1048 def ComputePatch(self):
1049 """Compute the patch (as a string of data) needed to turn sf into
1050 tf. Returns the same tuple as GetPatch()."""
1051
1052 tf = self.tf
1053 sf = self.sf
1054
Doug Zongker24cd2802012-08-14 16:36:15 -07001055 if self.diff_program:
1056 diff_program = self.diff_program
1057 else:
1058 ext = os.path.splitext(tf.name)[1]
1059 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001060
1061 ttemp = tf.WriteToTemp()
1062 stemp = sf.WriteToTemp()
1063
1064 ext = os.path.splitext(tf.name)[1]
1065
1066 try:
1067 ptemp = tempfile.NamedTemporaryFile()
1068 if isinstance(diff_program, list):
1069 cmd = copy.copy(diff_program)
1070 else:
1071 cmd = [diff_program]
1072 cmd.append(stemp.name)
1073 cmd.append(ttemp.name)
1074 cmd.append(ptemp.name)
1075 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001076 err = []
1077 def run():
1078 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001079 if e:
1080 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001081 th = threading.Thread(target=run)
1082 th.start()
1083 th.join(timeout=300) # 5 mins
1084 if th.is_alive():
1085 print "WARNING: diff command timed out"
1086 p.terminate()
1087 th.join(5)
1088 if th.is_alive():
1089 p.kill()
1090 th.join()
1091
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001092 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001093 print "WARNING: failure running %s:\n%s\n" % (
1094 diff_program, "".join(err))
1095 self.patch = None
1096 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001097 diff = ptemp.read()
1098 finally:
1099 ptemp.close()
1100 stemp.close()
1101 ttemp.close()
1102
1103 self.patch = diff
1104 return self.tf, self.sf, self.patch
1105
1106
1107 def GetPatch(self):
1108 """Return a tuple (target_file, source_file, patch_data).
1109 patch_data may be None if ComputePatch hasn't been called, or if
1110 computing the patch failed."""
1111 return self.tf, self.sf, self.patch
1112
1113
1114def ComputeDifferences(diffs):
1115 """Call ComputePatch on all the Difference objects in 'diffs'."""
1116 print len(diffs), "diffs to compute"
1117
1118 # Do the largest files first, to try and reduce the long-pole effect.
1119 by_size = [(i.tf.size, i) for i in diffs]
1120 by_size.sort(reverse=True)
1121 by_size = [i[1] for i in by_size]
1122
1123 lock = threading.Lock()
1124 diff_iter = iter(by_size) # accessed under lock
1125
1126 def worker():
1127 try:
1128 lock.acquire()
1129 for d in diff_iter:
1130 lock.release()
1131 start = time.time()
1132 d.ComputePatch()
1133 dur = time.time() - start
1134 lock.acquire()
1135
1136 tf, sf, patch = d.GetPatch()
1137 if sf.name == tf.name:
1138 name = tf.name
1139 else:
1140 name = "%s (%s)" % (tf.name, sf.name)
1141 if patch is None:
1142 print "patching failed! %s" % (name,)
1143 else:
1144 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1145 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1146 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001147 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001148 print e
1149 raise
1150
1151 # start worker threads; wait for them all to finish.
1152 threads = [threading.Thread(target=worker)
1153 for i in range(OPTIONS.worker_threads)]
1154 for th in threads:
1155 th.start()
1156 while threads:
1157 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001158
1159
Dan Albert8b72aef2015-03-23 19:13:21 -07001160class BlockDifference(object):
1161 def __init__(self, partition, tgt, src=None, check_first_block=False,
1162 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001163 self.tgt = tgt
1164 self.src = src
1165 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001166 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001167
Tao Bao5ece99d2015-05-12 11:42:31 -07001168 # Due to http://b/20939131, check_first_block is disabled temporarily.
1169 assert not self.check_first_block
1170
Tao Baodd2a5892015-03-12 12:32:37 -07001171 if version is None:
1172 version = 1
1173 if OPTIONS.info_dict:
1174 version = max(
1175 int(i) for i in
1176 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1177 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001178
1179 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001180 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001181 tmpdir = tempfile.mkdtemp()
1182 OPTIONS.tempfiles.append(tmpdir)
1183 self.path = os.path.join(tmpdir, partition)
1184 b.Compute(self.path)
1185
1186 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1187
1188 def WriteScript(self, script, output_zip, progress=None):
1189 if not self.src:
1190 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001191 script.Print("Patching %s image unconditionally..." % (self.partition,))
1192 else:
1193 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001194
Dan Albert8b72aef2015-03-23 19:13:21 -07001195 if progress:
1196 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001197 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001198 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001199
1200 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001201 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001202 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001203 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001204 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001205 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1206 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001207 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001208 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1209 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001210 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001211 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001212 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001213 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001214 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001215 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001216 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001217 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001218 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001219
Tao Baodd2a5892015-03-12 12:32:37 -07001220 # When generating incrementals for the system and vendor partitions,
1221 # explicitly check the first block (which contains the superblock) of
1222 # the partition to see if it's what we expect. If this check fails,
1223 # give an explicit log message about the partition having been
1224 # remounted R/W (the most likely explanation) and the need to flash to
1225 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001226 if self.check_first_block:
1227 self._CheckFirstBlock(script)
1228
Tao Baodd2a5892015-03-12 12:32:37 -07001229 # Abort the OTA update. Note that the incremental OTA cannot be applied
1230 # even if it may match the checksum of the target partition.
1231 # a) If version < 3, operations like move and erase will make changes
1232 # unconditionally and damage the partition.
1233 # b) If version >= 3, it won't even reach here.
1234 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1235 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001236
Tao Bao5fcaaef2015-06-01 13:40:49 -07001237 def _WritePostInstallVerifyScript(self, script):
1238 partition = self.partition
1239 script.Print('Verifying the updated %s image...' % (partition,))
1240 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1241 ranges = self.tgt.care_map
1242 ranges_str = ranges.to_string_raw()
1243 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1244 self.device, ranges_str,
1245 self.tgt.TotalSha1(include_clobbered_blocks=True)))
1246 script.Print('Verified the updated %s image.' % (partition,))
1247 script.AppendExtra(
1248 'else\n'
1249 ' abort("%s partition has unexpected contents after OTA update");\n'
1250 'endif;' % (partition,))
1251
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001252 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001253 ZipWrite(output_zip,
1254 '{}.transfer.list'.format(self.path),
1255 '{}.transfer.list'.format(self.partition))
1256 ZipWrite(output_zip,
1257 '{}.new.dat'.format(self.path),
1258 '{}.new.dat'.format(self.partition))
1259 ZipWrite(output_zip,
1260 '{}.patch.dat'.format(self.path),
1261 '{}.patch.dat'.format(self.partition),
1262 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001263
Dan Albert8e0178d2015-01-27 15:53:15 -08001264 call = ('block_image_update("{device}", '
1265 'package_extract_file("{partition}.transfer.list"), '
1266 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1267 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001269
Dan Albert8b72aef2015-03-23 19:13:21 -07001270 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001271 data = source.ReadRangeSet(ranges)
1272 ctx = sha1()
1273
1274 for p in data:
1275 ctx.update(p)
1276
1277 return ctx.hexdigest()
1278
Tao Bao5ece99d2015-05-12 11:42:31 -07001279 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1280 # remounting R/W. Will change the checking to a finer-grained way to
1281 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001282 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001283 r = rangelib.RangeSet((0, 1))
1284 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001285
1286 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1287 'abort("%s has been remounted R/W; '
1288 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001289 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001290 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001291
1292DataImage = blockimgdiff.DataImage
1293
1294
Doug Zongker96a57e72010-09-26 14:57:41 -07001295# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001296PARTITION_TYPES = {
1297 "yaffs2": "MTD",
1298 "mtd": "MTD",
1299 "ext4": "EMMC",
1300 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001301 "f2fs": "EMMC",
1302 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001303}
Doug Zongker96a57e72010-09-26 14:57:41 -07001304
1305def GetTypeAndDevice(mount_point, info):
1306 fstab = info["fstab"]
1307 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001308 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1309 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001310 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001311 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001312
1313
1314def ParseCertificate(data):
1315 """Parse a PEM-format certificate."""
1316 cert = []
1317 save = False
1318 for line in data.split("\n"):
1319 if "--END CERTIFICATE--" in line:
1320 break
1321 if save:
1322 cert.append(line)
1323 if "--BEGIN CERTIFICATE--" in line:
1324 save = True
1325 cert = "".join(cert).decode('base64')
1326 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001327
Doug Zongker412c02f2014-02-13 10:58:24 -08001328def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1329 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001330 """Generate a binary patch that creates the recovery image starting
1331 with the boot image. (Most of the space in these images is just the
1332 kernel, which is identical for the two, so the resulting patch
1333 should be efficient.) Add it to the output zip, along with a shell
1334 script that is run from init.rc on first boot to actually do the
1335 patching and install the new recovery image.
1336
1337 recovery_img and boot_img should be File objects for the
1338 corresponding images. info should be the dictionary returned by
1339 common.LoadInfoDict() on the input target_files.
1340 """
1341
Doug Zongker412c02f2014-02-13 10:58:24 -08001342 if info_dict is None:
1343 info_dict = OPTIONS.info_dict
1344
Doug Zongkerc9253822014-02-04 12:17:58 -08001345 diff_program = ["imgdiff"]
1346 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1347 if os.path.exists(path):
1348 diff_program.append("-b")
1349 diff_program.append(path)
1350 bonus_args = "-b /system/etc/recovery-resource.dat"
1351 else:
1352 bonus_args = ""
1353
1354 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1355 _, _, patch = d.ComputePatch()
1356 output_sink("recovery-from-boot.p", patch)
1357
Dan Albertebb19aa2015-03-27 19:11:53 -07001358 try:
1359 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1360 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1361 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001362 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001363
1364 sh = """#!/system/bin/sh
1365if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1366 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"
1367else
1368 log -t recovery "Recovery image already installed"
1369fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001370""" % {'boot_size': boot_img.size,
1371 'boot_sha1': boot_img.sha1,
1372 'recovery_size': recovery_img.size,
1373 'recovery_sha1': recovery_img.sha1,
1374 'boot_type': boot_type,
1375 'boot_device': boot_device,
1376 'recovery_type': recovery_type,
1377 'recovery_device': recovery_device,
1378 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001379
1380 # The install script location moved from /system/etc to /system/bin
1381 # in the L release. Parse the init.rc file to find out where the
1382 # target-files expects it to be, and put it there.
1383 sh_location = "etc/install-recovery.sh"
1384 try:
1385 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1386 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001387 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001388 if m:
1389 sh_location = m.group(1)
1390 print "putting script in", sh_location
1391 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001392 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001393 print "failed to read init.rc: %s" % (e,)
1394
1395 output_sink(sh_location, sh)