blob: 3944af5169945d61e98a60450f0cd46cae83461c [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080021import platform
Doug Zongkereef39442009-04-02 12:14:19 -070022import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070023import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070024import shutil
25import subprocess
26import sys
27import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070028import threading
29import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070030import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070031
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070032import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070033import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034
Tao Baof3282b42015-04-01 11:21:55 -070035from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080036
Doug Zongkereef39442009-04-02 12:14:19 -070037
Dan Albert8b72aef2015-03-23 19:13:21 -070038class Options(object):
39 def __init__(self):
40 platform_search_path = {
41 "linux2": "out/host/linux-x86",
42 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070043 }
Doug Zongker85448772014-09-09 14:59:20 -070044
Dan Albert8b72aef2015-03-23 19:13:21 -070045 self.search_path = platform_search_path.get(sys.platform, None)
46 self.signapk_path = "framework/signapk.jar" # Relative to search_path
47 self.extra_signapk_args = []
48 self.java_path = "java" # Use the one on the path by default.
49 self.java_args = "-Xmx2048m" # JVM Args
50 self.public_key_suffix = ".x509.pem"
51 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070052 # use otatools built boot_signer by default
53 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070054 self.boot_signer_args = []
55 self.verity_signer_path = None
56 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.verbose = False
58 self.tempfiles = []
59 self.device_specific = None
60 self.extras = {}
61 self.info_dict = None
62 self.worker_threads = None
63
64
65OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070066
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080067
68# Values for "certificate" in apkcerts that mean special things.
69SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
70
71
Dan Albert8b72aef2015-03-23 19:13:21 -070072class ExternalError(RuntimeError):
73 pass
Doug Zongkereef39442009-04-02 12:14:19 -070074
75
76def Run(args, **kwargs):
77 """Create and return a subprocess.Popen object, printing the command
78 line on the terminal if -v was specified."""
79 if OPTIONS.verbose:
80 print " running: ", " ".join(args)
81 return subprocess.Popen(args, **kwargs)
82
83
Ying Wang7e6d4e42010-12-13 16:25:36 -080084def CloseInheritedPipes():
85 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
86 before doing other work."""
87 if platform.system() != "Darwin":
88 return
89 for d in range(3, 1025):
90 try:
91 stat = os.fstat(d)
92 if stat is not None:
93 pipebit = stat[0] & 0x1000
94 if pipebit != 0:
95 os.close(d)
96 except OSError:
97 pass
98
99
Dan Albert8b72aef2015-03-23 19:13:21 -0700100def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700101 """Read and parse the META/misc_info.txt key/value pairs from the
102 input target files and return a dict."""
103
Doug Zongkerc9253822014-02-04 12:17:58 -0800104 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700105 if isinstance(input_file, zipfile.ZipFile):
106 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800107 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700108 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800109 try:
110 with open(path) as f:
111 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 if e.errno == errno.ENOENT:
114 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700115 d = {}
116 try:
Michael Runge6e836112014-04-15 17:40:21 -0700117 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700118 except KeyError:
119 # ok if misc_info.txt doesn't exist
120 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121
Doug Zongker37974732010-09-16 17:44:38 -0700122 # backwards compatibility: These values used to be in their own
123 # files. Look for them, in case we're processing an old
124 # target_files zip.
125
126 if "mkyaffs2_extra_flags" not in d:
127 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700128 d["mkyaffs2_extra_flags"] = read_helper(
129 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700130 except KeyError:
131 # ok if flags don't exist
132 pass
133
134 if "recovery_api_version" not in d:
135 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700136 d["recovery_api_version"] = read_helper(
137 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700138 except KeyError:
139 raise ValueError("can't find recovery API version in input target-files")
140
141 if "tool_extensions" not in d:
142 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 # ok if extensions don't exist
146 pass
147
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800148 if "fstab_version" not in d:
149 d["fstab_version"] = "1"
150
Doug Zongker37974732010-09-16 17:44:38 -0700151 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700153 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700154 if not line:
155 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700156 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700157 if not value:
158 continue
Doug Zongker37974732010-09-16 17:44:38 -0700159 if name == "blocksize":
160 d[name] = value
161 else:
162 d[name + "_size"] = value
163 except KeyError:
164 pass
165
166 def makeint(key):
167 if key in d:
168 d[key] = int(d[key], 0)
169
170 makeint("recovery_api_version")
171 makeint("blocksize")
172 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700173 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700174 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700175 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("recovery_size")
177 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800178 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700179
Doug Zongkerc9253822014-02-04 12:17:58 -0800180 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"])
181 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700182 return d
183
Doug Zongkerc9253822014-02-04 12:17:58 -0800184def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700185 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800186 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 except KeyError:
188 print "Warning: could not find SYSTEM/build.prop in %s" % zip
189 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700190 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191
Michael Runge6e836112014-04-15 17:40:21 -0700192def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700194 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700196 if not line or line.startswith("#"):
197 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700198 if "=" in line:
199 name, value = line.split("=", 1)
200 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700201 return d
202
Doug Zongkerc9253822014-02-04 12:17:58 -0800203def LoadRecoveryFSTab(read_helper, fstab_version):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700204 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700205 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700206 self.mount_point = mount_point
207 self.fs_type = fs_type
208 self.device = device
209 self.length = length
210 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700211 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212
213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700215 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700217 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800219 if fstab_version == 1:
220 d = {}
221 for line in data.split("\n"):
222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700223 if not line or line.startswith("#"):
224 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700226 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800228 options = None
229 if len(pieces) >= 4:
230 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800232 if len(pieces) >= 5:
233 options = pieces[4]
234 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700235 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800237 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700238 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 mount_point = pieces[0]
241 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 if options:
243 options = options.split(",")
244 for i in options:
245 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800247 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
251 device=pieces[2], length=length,
252 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253
254 elif fstab_version == 2:
255 d = {}
256 for line in data.split("\n"):
257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 if not line or line.startswith("#"):
259 continue
Tao Bao548eb762015-06-10 12:32:41 -0700260 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261 pieces = line.split()
262 if len(pieces) != 5:
263 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
264
265 # Ignore entries that are managed by vold
266 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 if "voldmanaged=" in options:
268 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269
270 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800272 options = options.split(",")
273 for i in options:
274 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800276 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277 # Ignore all unknown options in the unified fstab
278 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800279
Tao Bao548eb762015-06-10 12:32:41 -0700280 mount_flags = pieces[3]
281 # Honor the SELinux context if present.
282 context = None
283 for i in mount_flags.split(","):
284 if i.startswith("context="):
285 context = i
286
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 mount_point = pieces[1]
288 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700289 device=pieces[0], length=length,
290 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800291
292 else:
293 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
294
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700295 return d
296
297
Doug Zongker37974732010-09-16 17:44:38 -0700298def DumpInfoDict(d):
299 for k, v in sorted(d.items()):
300 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301
Dan Albert8b72aef2015-03-23 19:13:21 -0700302
Doug Zongkerd5131602012-08-02 14:46:42 -0700303def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700304 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700305 'sourcedir'), and turn them into a boot image. Return the image
306 data, or None if sourcedir does not appear to contains files for
307 building the requested image."""
308
309 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
310 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
311 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700312
Doug Zongkerd5131602012-08-02 14:46:42 -0700313 if info_dict is None:
314 info_dict = OPTIONS.info_dict
315
Doug Zongkereef39442009-04-02 12:14:19 -0700316 ramdisk_img = tempfile.NamedTemporaryFile()
317 img = tempfile.NamedTemporaryFile()
318
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700319 if os.access(fs_config_file, os.F_OK):
320 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
321 else:
322 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
323 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700324 p2 = Run(["minigzip"],
325 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700326
327 p2.wait()
328 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
330 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700331
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000332 """check if uboot is requested"""
333 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200334 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000335 cmd = ["mkimage"]
336 for argument in open(fn).read().rstrip("\n").split(" "):
337 cmd.append(argument)
338 cmd.append("-d")
339 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
340 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200341
Tao Baod95e9fd2015-03-29 23:07:41 -0700342 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000343 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
344 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
345 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700346
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000347 fn = os.path.join(sourcedir, "second")
348 if os.access(fn, os.F_OK):
349 cmd.append("--second")
350 cmd.append(fn)
351
352 fn = os.path.join(sourcedir, "cmdline")
353 if os.access(fn, os.F_OK):
354 cmd.append("--cmdline")
355 cmd.append(open(fn).read().rstrip("\n"))
356
357 fn = os.path.join(sourcedir, "base")
358 if os.access(fn, os.F_OK):
359 cmd.append("--base")
360 cmd.append(open(fn).read().rstrip("\n"))
361
362 fn = os.path.join(sourcedir, "tagsaddr")
363 if os.access(fn, os.F_OK):
364 cmd.append("--tags-addr")
365 cmd.append(open(fn).read().rstrip("\n"))
366
367 fn = os.path.join(sourcedir, "ramdisk_offset")
368 if os.access(fn, os.F_OK):
369 cmd.append("--ramdisk_offset")
370 cmd.append(open(fn).read().rstrip("\n"))
371
372 fn = os.path.join(sourcedir, "dt_args")
373 if os.access(fn, os.F_OK):
374 cmd.append("--dt")
375 cmd.append(open(fn).read().rstrip("\n"))
376
377 fn = os.path.join(sourcedir, "pagesize")
378 if os.access(fn, os.F_OK):
379 cmd.append("--pagesize")
380 cmd.append(open(fn).read().rstrip("\n"))
381
382 args = info_dict.get("mkbootimg_args", None)
383 if args and args.strip():
384 cmd.extend(shlex.split(args))
385
386 img_unsigned = None
387 if info_dict.get("vboot", None):
388 img_unsigned = tempfile.NamedTemporaryFile()
389 cmd.extend(["--ramdisk", ramdisk_img.name,
390 "--output", img_unsigned.name])
391 else:
392 cmd.extend(["--ramdisk", ramdisk_img.name,
393 "--output", img.name])
394
Doug Zongker38a649f2009-06-17 09:07:09 -0700395 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700396 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700397 assert p.returncode == 0, "mkbootimg of %s image failed" % (
398 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700399
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100400 if (info_dict.get("boot_signer", None) == "true" and
401 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700402 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700403 cmd = [OPTIONS.boot_signer_path]
404 cmd.extend(OPTIONS.boot_signer_args)
405 cmd.extend([path, img.name,
406 info_dict["verity_key"] + ".pk8",
407 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700408 p = Run(cmd, stdout=subprocess.PIPE)
409 p.communicate()
410 assert p.returncode == 0, "boot_signer of %s image failed" % path
411
Tao Baod95e9fd2015-03-29 23:07:41 -0700412 # Sign the image if vboot is non-empty.
413 elif info_dict.get("vboot", None):
414 path = "/" + os.path.basename(sourcedir).lower()
415 img_keyblock = tempfile.NamedTemporaryFile()
416 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
417 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
418 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
419 img.name]
420 p = Run(cmd, stdout=subprocess.PIPE)
421 p.communicate()
422 assert p.returncode == 0, "vboot_signer of %s image failed" % path
423
Tao Baof3282b42015-04-01 11:21:55 -0700424 # Clean up the temp files.
425 img_unsigned.close()
426 img_keyblock.close()
427
Doug Zongkereef39442009-04-02 12:14:19 -0700428 img.seek(os.SEEK_SET, 0)
429 data = img.read()
430
431 ramdisk_img.close()
432 img.close()
433
434 return data
435
436
Doug Zongkerd5131602012-08-02 14:46:42 -0700437def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
438 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800439 """Return a File object (with name 'name') with the desired bootable
440 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700441 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
442 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800443 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700444
Doug Zongker55d93282011-01-25 17:03:34 -0800445 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
446 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700447 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800448 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700449
450 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
451 if os.path.exists(prebuilt_path):
452 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
453 return File.FromLocalFile(name, prebuilt_path)
454
455 print "building image from target_files %s..." % (tree_subdir,)
456 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
457 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
458 os.path.join(unpack_dir, fs_config),
459 info_dict)
460 if data:
461 return File(name, data)
462 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800463
Doug Zongkereef39442009-04-02 12:14:19 -0700464
Doug Zongker75f17362009-12-08 13:46:44 -0800465def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800466 """Unzip the given archive into a temporary directory and return the name.
467
468 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
469 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
470
471 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
472 main file), open for reading.
473 """
Doug Zongkereef39442009-04-02 12:14:19 -0700474
475 tmp = tempfile.mkdtemp(prefix="targetfiles-")
476 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800477
478 def unzip_to_dir(filename, dirname):
479 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
480 if pattern is not None:
481 cmd.append(pattern)
482 p = Run(cmd, stdout=subprocess.PIPE)
483 p.communicate()
484 if p.returncode != 0:
485 raise ExternalError("failed to unzip input target-files \"%s\"" %
486 (filename,))
487
488 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
489 if m:
490 unzip_to_dir(m.group(1), tmp)
491 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
492 filename = m.group(1)
493 else:
494 unzip_to_dir(filename, tmp)
495
496 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700497
498
499def GetKeyPasswords(keylist):
500 """Given a list of keys, prompt the user to enter passwords for
501 those which require them. Return a {key: password} dict. password
502 will be None if the key has no password."""
503
Doug Zongker8ce7c252009-05-22 13:34:54 -0700504 no_passwords = []
505 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700506 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700507 devnull = open("/dev/null", "w+b")
508 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800509 # We don't need a password for things that aren't really keys.
510 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700511 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700512 continue
513
T.R. Fullhart37e10522013-03-18 10:31:26 -0700514 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700515 "-inform", "DER", "-nocrypt"],
516 stdin=devnull.fileno(),
517 stdout=devnull.fileno(),
518 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700519 p.communicate()
520 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700521 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700522 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700523 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700524 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
525 "-inform", "DER", "-passin", "pass:"],
526 stdin=devnull.fileno(),
527 stdout=devnull.fileno(),
528 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700529 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700530 if p.returncode == 0:
531 # Encrypted key with empty string as password.
532 key_passwords[k] = ''
533 elif stderr.startswith('Error decrypting key'):
534 # Definitely encrypted key.
535 # It would have said "Error reading key" if it didn't parse correctly.
536 need_passwords.append(k)
537 else:
538 # Potentially, a type of key that openssl doesn't understand.
539 # We'll let the routines in signapk.jar handle it.
540 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700541 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700542
T.R. Fullhart37e10522013-03-18 10:31:26 -0700543 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700544 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700545 return key_passwords
546
547
Doug Zongker951495f2009-08-14 12:44:19 -0700548def SignFile(input_name, output_name, key, password, align=None,
549 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700550 """Sign the input_name zip/jar/apk, producing output_name. Use the
551 given key and password (the latter may be None if the key does not
552 have a password.
553
554 If align is an integer > 1, zipalign is run to align stored files in
555 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700556
557 If whole_file is true, use the "-w" option to SignApk to embed a
558 signature that covers the whole file in the archive comment of the
559 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700560 """
Doug Zongker951495f2009-08-14 12:44:19 -0700561
Doug Zongkereef39442009-04-02 12:14:19 -0700562 if align == 0 or align == 1:
563 align = None
564
565 if align:
566 temp = tempfile.NamedTemporaryFile()
567 sign_name = temp.name
568 else:
569 sign_name = output_name
570
Baligh Uddin339ee492014-09-05 11:18:07 -0700571 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700572 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
573 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700574 if whole_file:
575 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700576 cmd.extend([key + OPTIONS.public_key_suffix,
577 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700578 input_name, sign_name])
579
580 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700581 if password is not None:
582 password += "\n"
583 p.communicate(password)
584 if p.returncode != 0:
585 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
586
587 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700588 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700589 p.communicate()
590 if p.returncode != 0:
591 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
592 temp.close()
593
594
Doug Zongker37974732010-09-16 17:44:38 -0700595def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700596 """Check the data string passed against the max size limit, if
597 any, for the given target. Raise exception if the data is too big.
598 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700599
Dan Albert8b72aef2015-03-23 19:13:21 -0700600 if target.endswith(".img"):
601 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700602 mount_point = "/" + target
603
Ying Wangf8824af2014-06-03 14:07:27 -0700604 fs_type = None
605 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700606 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700607 if mount_point == "/userdata":
608 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700609 p = info_dict["fstab"][mount_point]
610 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800611 device = p.device
612 if "/" in device:
613 device = device[device.rfind("/")+1:]
614 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700615 if not fs_type or not limit:
616 return
Doug Zongkereef39442009-04-02 12:14:19 -0700617
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700618 if fs_type == "yaffs2":
619 # image size should be increased by 1/64th to account for the
620 # spare area (64 bytes per 2k page)
621 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800622 size = len(data)
623 pct = float(size) * 100.0 / limit
624 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
625 if pct >= 99.0:
626 raise ExternalError(msg)
627 elif pct >= 95.0:
628 print
629 print " WARNING: ", msg
630 print
631 elif OPTIONS.verbose:
632 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700633
634
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800635def ReadApkCerts(tf_zip):
636 """Given a target_files ZipFile, parse the META/apkcerts.txt file
637 and return a {package: cert} dict."""
638 certmap = {}
639 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
640 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700641 if not line:
642 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800643 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
644 r'private_key="(.*)"$', line)
645 if m:
646 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700647 public_key_suffix_len = len(OPTIONS.public_key_suffix)
648 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800649 if cert in SPECIAL_CERT_STRINGS and not privkey:
650 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700651 elif (cert.endswith(OPTIONS.public_key_suffix) and
652 privkey.endswith(OPTIONS.private_key_suffix) and
653 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
654 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800655 else:
656 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
657 return certmap
658
659
Doug Zongkereef39442009-04-02 12:14:19 -0700660COMMON_DOCSTRING = """
661 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700662 Prepend <dir>/bin to the list of places to search for binaries
663 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700664
Doug Zongker05d3dea2009-06-22 11:32:31 -0700665 -s (--device_specific) <file>
666 Path to the python module containing device-specific
667 releasetools code.
668
Doug Zongker8bec09e2009-11-30 15:37:14 -0800669 -x (--extra) <key=value>
670 Add a key/value pair to the 'extras' dict, which device-specific
671 extension code may look at.
672
Doug Zongkereef39442009-04-02 12:14:19 -0700673 -v (--verbose)
674 Show command lines being executed.
675
676 -h (--help)
677 Display this usage message and exit.
678"""
679
680def Usage(docstring):
681 print docstring.rstrip("\n")
682 print COMMON_DOCSTRING
683
684
685def ParseOptions(argv,
686 docstring,
687 extra_opts="", extra_long_opts=(),
688 extra_option_handler=None):
689 """Parse the options in argv and return any arguments that aren't
690 flags. docstring is the calling module's docstring, to be displayed
691 for errors and -h. extra_opts and extra_long_opts are for flags
692 defined by the caller, which are processed by passing them to
693 extra_option_handler."""
694
695 try:
696 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800697 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700698 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700699 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700700 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
701 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800702 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700703 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700704 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700705 Usage(docstring)
706 print "**", str(err), "**"
707 sys.exit(2)
708
Doug Zongkereef39442009-04-02 12:14:19 -0700709 for o, a in opts:
710 if o in ("-h", "--help"):
711 Usage(docstring)
712 sys.exit()
713 elif o in ("-v", "--verbose"):
714 OPTIONS.verbose = True
715 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700716 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 elif o in ("--signapk_path",):
718 OPTIONS.signapk_path = a
719 elif o in ("--extra_signapk_args",):
720 OPTIONS.extra_signapk_args = shlex.split(a)
721 elif o in ("--java_path",):
722 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700723 elif o in ("--java_args",):
724 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700725 elif o in ("--public_key_suffix",):
726 OPTIONS.public_key_suffix = a
727 elif o in ("--private_key_suffix",):
728 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800729 elif o in ("--boot_signer_path",):
730 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700731 elif o in ("--boot_signer_args",):
732 OPTIONS.boot_signer_args = shlex.split(a)
733 elif o in ("--verity_signer_path",):
734 OPTIONS.verity_signer_path = a
735 elif o in ("--verity_signer_args",):
736 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700737 elif o in ("-s", "--device_specific"):
738 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800739 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800740 key, value = a.split("=", 1)
741 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700742 else:
743 if extra_option_handler is None or not extra_option_handler(o, a):
744 assert False, "unknown option \"%s\"" % (o,)
745
Doug Zongker85448772014-09-09 14:59:20 -0700746 if OPTIONS.search_path:
747 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
748 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700749
750 return args
751
752
Doug Zongkerfc44a512014-08-26 13:10:25 -0700753def MakeTempFile(prefix=None, suffix=None):
754 """Make a temp file and add it to the list of things to be deleted
755 when Cleanup() is called. Return the filename."""
756 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
757 os.close(fd)
758 OPTIONS.tempfiles.append(fn)
759 return fn
760
761
Doug Zongkereef39442009-04-02 12:14:19 -0700762def Cleanup():
763 for i in OPTIONS.tempfiles:
764 if os.path.isdir(i):
765 shutil.rmtree(i)
766 else:
767 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700768
769
770class PasswordManager(object):
771 def __init__(self):
772 self.editor = os.getenv("EDITOR", None)
773 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
774
775 def GetPasswords(self, items):
776 """Get passwords corresponding to each string in 'items',
777 returning a dict. (The dict may have keys in addition to the
778 values in 'items'.)
779
780 Uses the passwords in $ANDROID_PW_FILE if available, letting the
781 user edit that file to add more needed passwords. If no editor is
782 available, or $ANDROID_PW_FILE isn't define, prompts the user
783 interactively in the ordinary way.
784 """
785
786 current = self.ReadFile()
787
788 first = True
789 while True:
790 missing = []
791 for i in items:
792 if i not in current or not current[i]:
793 missing.append(i)
794 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700795 if not missing:
796 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700797
798 for i in missing:
799 current[i] = ""
800
801 if not first:
802 print "key file %s still missing some passwords." % (self.pwfile,)
803 answer = raw_input("try to edit again? [y]> ").strip()
804 if answer and answer[0] not in 'yY':
805 raise RuntimeError("key passwords unavailable")
806 first = False
807
808 current = self.UpdateAndReadFile(current)
809
Dan Albert8b72aef2015-03-23 19:13:21 -0700810 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700811 """Prompt the user to enter a value (password) for each key in
812 'current' whose value is fales. Returns a new dict with all the
813 values.
814 """
815 result = {}
816 for k, v in sorted(current.iteritems()):
817 if v:
818 result[k] = v
819 else:
820 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700821 result[k] = getpass.getpass(
822 "Enter password for %s key> " % k).strip()
823 if result[k]:
824 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700825 return result
826
827 def UpdateAndReadFile(self, current):
828 if not self.editor or not self.pwfile:
829 return self.PromptResult(current)
830
831 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700832 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
834 f.write("# (Additional spaces are harmless.)\n\n")
835
836 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
838 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839 f.write("[[[ %s ]]] %s\n" % (v, k))
840 if not v and first_line is None:
841 # position cursor on first line with no password.
842 first_line = i + 4
843 f.close()
844
845 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
846 _, _ = p.communicate()
847
848 return self.ReadFile()
849
850 def ReadFile(self):
851 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 if self.pwfile is None:
853 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 try:
855 f = open(self.pwfile, "r")
856 for line in f:
857 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700858 if not line or line[0] == '#':
859 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700860 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
861 if not m:
862 print "failed to parse password file: ", line
863 else:
864 result[m.group(2)] = m.group(1)
865 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700867 if e.errno != errno.ENOENT:
868 print "error reading password file: ", str(e)
869 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700870
871
Dan Albert8e0178d2015-01-27 15:53:15 -0800872def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
873 compress_type=None):
874 import datetime
875
876 # http://b/18015246
877 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
878 # for files larger than 2GiB. We can work around this by adjusting their
879 # limit. Note that `zipfile.writestr()` will not work for strings larger than
880 # 2GiB. The Python interpreter sometimes rejects strings that large (though
881 # it isn't clear to me exactly what circumstances cause this).
882 # `zipfile.write()` must be used directly to work around this.
883 #
884 # This mess can be avoided if we port to python3.
885 saved_zip64_limit = zipfile.ZIP64_LIMIT
886 zipfile.ZIP64_LIMIT = (1 << 32) - 1
887
888 if compress_type is None:
889 compress_type = zip_file.compression
890 if arcname is None:
891 arcname = filename
892
893 saved_stat = os.stat(filename)
894
895 try:
896 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
897 # file to be zipped and reset it when we're done.
898 os.chmod(filename, perms)
899
900 # Use a fixed timestamp so the output is repeatable.
901 epoch = datetime.datetime.fromtimestamp(0)
902 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
903 os.utime(filename, (timestamp, timestamp))
904
905 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
906 finally:
907 os.chmod(filename, saved_stat.st_mode)
908 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
909 zipfile.ZIP64_LIMIT = saved_zip64_limit
910
911
Tao Bao58c1b962015-05-20 09:32:18 -0700912def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700913 compress_type=None):
914 """Wrap zipfile.writestr() function to work around the zip64 limit.
915
916 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
917 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
918 when calling crc32(bytes).
919
920 But it still works fine to write a shorter string into a large zip file.
921 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
922 when we know the string won't be too long.
923 """
924
925 saved_zip64_limit = zipfile.ZIP64_LIMIT
926 zipfile.ZIP64_LIMIT = (1 << 32) - 1
927
928 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
929 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700930 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700931 if perms is None:
932 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800933 else:
Tao Baof3282b42015-04-01 11:21:55 -0700934 zinfo = zinfo_or_arcname
935
936 # If compress_type is given, it overrides the value in zinfo.
937 if compress_type is not None:
938 zinfo.compress_type = compress_type
939
Tao Bao58c1b962015-05-20 09:32:18 -0700940 # If perms is given, it has a priority.
941 if perms is not None:
942 zinfo.external_attr = perms << 16
943
Tao Baof3282b42015-04-01 11:21:55 -0700944 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700945 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
946
Dan Albert8b72aef2015-03-23 19:13:21 -0700947 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700948 zipfile.ZIP64_LIMIT = saved_zip64_limit
949
950
951def ZipClose(zip_file):
952 # http://b/18015246
953 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
954 # central directory.
955 saved_zip64_limit = zipfile.ZIP64_LIMIT
956 zipfile.ZIP64_LIMIT = (1 << 32) - 1
957
958 zip_file.close()
959
960 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700961
962
963class DeviceSpecificParams(object):
964 module = None
965 def __init__(self, **kwargs):
966 """Keyword arguments to the constructor become attributes of this
967 object, which is passed to all functions in the device-specific
968 module."""
969 for k, v in kwargs.iteritems():
970 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800971 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700972
973 if self.module is None:
974 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700975 if not path:
976 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700977 try:
978 if os.path.isdir(path):
979 info = imp.find_module("releasetools", [path])
980 else:
981 d, f = os.path.split(path)
982 b, x = os.path.splitext(f)
983 if x == ".py":
984 f = b
985 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800986 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700987 self.module = imp.load_module("device_specific", *info)
988 except ImportError:
989 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990
991 def _DoCall(self, function_name, *args, **kwargs):
992 """Call the named function in the device-specific module, passing
993 the given args and kwargs. The first argument to the call will be
994 the DeviceSpecific object itself. If there is no module, or the
995 module does not define the function, return the value of the
996 'default' kwarg (which itself defaults to None)."""
997 if self.module is None or not hasattr(self.module, function_name):
998 return kwargs.get("default", None)
999 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1000
1001 def FullOTA_Assertions(self):
1002 """Called after emitting the block of assertions at the top of a
1003 full OTA package. Implementations can add whatever additional
1004 assertions they like."""
1005 return self._DoCall("FullOTA_Assertions")
1006
Doug Zongkere5ff5902012-01-17 10:55:37 -08001007 def FullOTA_InstallBegin(self):
1008 """Called at the start of full OTA installation."""
1009 return self._DoCall("FullOTA_InstallBegin")
1010
Doug Zongker05d3dea2009-06-22 11:32:31 -07001011 def FullOTA_InstallEnd(self):
1012 """Called at the end of full OTA installation; typically this is
1013 used to install the image for the device's baseband processor."""
1014 return self._DoCall("FullOTA_InstallEnd")
1015
1016 def IncrementalOTA_Assertions(self):
1017 """Called after emitting the block of assertions at the top of an
1018 incremental OTA package. Implementations can add whatever
1019 additional assertions they like."""
1020 return self._DoCall("IncrementalOTA_Assertions")
1021
Doug Zongkere5ff5902012-01-17 10:55:37 -08001022 def IncrementalOTA_VerifyBegin(self):
1023 """Called at the start of the verification phase of incremental
1024 OTA installation; additional checks can be placed here to abort
1025 the script before any changes are made."""
1026 return self._DoCall("IncrementalOTA_VerifyBegin")
1027
Doug Zongker05d3dea2009-06-22 11:32:31 -07001028 def IncrementalOTA_VerifyEnd(self):
1029 """Called at the end of the verification phase of incremental OTA
1030 installation; additional checks can be placed here to abort the
1031 script before any changes are made."""
1032 return self._DoCall("IncrementalOTA_VerifyEnd")
1033
Doug Zongkere5ff5902012-01-17 10:55:37 -08001034 def IncrementalOTA_InstallBegin(self):
1035 """Called at the start of incremental OTA installation (after
1036 verification is complete)."""
1037 return self._DoCall("IncrementalOTA_InstallBegin")
1038
Doug Zongker05d3dea2009-06-22 11:32:31 -07001039 def IncrementalOTA_InstallEnd(self):
1040 """Called at the end of incremental OTA installation; typically
1041 this is used to install the image for the device's baseband
1042 processor."""
1043 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001044
1045class File(object):
1046 def __init__(self, name, data):
1047 self.name = name
1048 self.data = data
1049 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001050 self.sha1 = sha1(data).hexdigest()
1051
1052 @classmethod
1053 def FromLocalFile(cls, name, diskname):
1054 f = open(diskname, "rb")
1055 data = f.read()
1056 f.close()
1057 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001058
1059 def WriteToTemp(self):
1060 t = tempfile.NamedTemporaryFile()
1061 t.write(self.data)
1062 t.flush()
1063 return t
1064
Geremy Condra36bd3652014-02-06 19:45:10 -08001065 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001066 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001067
1068DIFF_PROGRAM_BY_EXT = {
1069 ".gz" : "imgdiff",
1070 ".zip" : ["imgdiff", "-z"],
1071 ".jar" : ["imgdiff", "-z"],
1072 ".apk" : ["imgdiff", "-z"],
1073 ".img" : "imgdiff",
1074 }
1075
1076class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001077 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001078 self.tf = tf
1079 self.sf = sf
1080 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001081 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001082
1083 def ComputePatch(self):
1084 """Compute the patch (as a string of data) needed to turn sf into
1085 tf. Returns the same tuple as GetPatch()."""
1086
1087 tf = self.tf
1088 sf = self.sf
1089
Doug Zongker24cd2802012-08-14 16:36:15 -07001090 if self.diff_program:
1091 diff_program = self.diff_program
1092 else:
1093 ext = os.path.splitext(tf.name)[1]
1094 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001095
1096 ttemp = tf.WriteToTemp()
1097 stemp = sf.WriteToTemp()
1098
1099 ext = os.path.splitext(tf.name)[1]
1100
1101 try:
1102 ptemp = tempfile.NamedTemporaryFile()
1103 if isinstance(diff_program, list):
1104 cmd = copy.copy(diff_program)
1105 else:
1106 cmd = [diff_program]
1107 cmd.append(stemp.name)
1108 cmd.append(ttemp.name)
1109 cmd.append(ptemp.name)
1110 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001111 err = []
1112 def run():
1113 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001114 if e:
1115 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001116 th = threading.Thread(target=run)
1117 th.start()
1118 th.join(timeout=300) # 5 mins
1119 if th.is_alive():
1120 print "WARNING: diff command timed out"
1121 p.terminate()
1122 th.join(5)
1123 if th.is_alive():
1124 p.kill()
1125 th.join()
1126
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001127 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001128 print "WARNING: failure running %s:\n%s\n" % (
1129 diff_program, "".join(err))
1130 self.patch = None
1131 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001132 diff = ptemp.read()
1133 finally:
1134 ptemp.close()
1135 stemp.close()
1136 ttemp.close()
1137
1138 self.patch = diff
1139 return self.tf, self.sf, self.patch
1140
1141
1142 def GetPatch(self):
1143 """Return a tuple (target_file, source_file, patch_data).
1144 patch_data may be None if ComputePatch hasn't been called, or if
1145 computing the patch failed."""
1146 return self.tf, self.sf, self.patch
1147
1148
1149def ComputeDifferences(diffs):
1150 """Call ComputePatch on all the Difference objects in 'diffs'."""
1151 print len(diffs), "diffs to compute"
1152
1153 # Do the largest files first, to try and reduce the long-pole effect.
1154 by_size = [(i.tf.size, i) for i in diffs]
1155 by_size.sort(reverse=True)
1156 by_size = [i[1] for i in by_size]
1157
1158 lock = threading.Lock()
1159 diff_iter = iter(by_size) # accessed under lock
1160
1161 def worker():
1162 try:
1163 lock.acquire()
1164 for d in diff_iter:
1165 lock.release()
1166 start = time.time()
1167 d.ComputePatch()
1168 dur = time.time() - start
1169 lock.acquire()
1170
1171 tf, sf, patch = d.GetPatch()
1172 if sf.name == tf.name:
1173 name = tf.name
1174 else:
1175 name = "%s (%s)" % (tf.name, sf.name)
1176 if patch is None:
1177 print "patching failed! %s" % (name,)
1178 else:
1179 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1180 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1181 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001182 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001183 print e
1184 raise
1185
1186 # start worker threads; wait for them all to finish.
1187 threads = [threading.Thread(target=worker)
1188 for i in range(OPTIONS.worker_threads)]
1189 for th in threads:
1190 th.start()
1191 while threads:
1192 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001193
1194
Dan Albert8b72aef2015-03-23 19:13:21 -07001195class BlockDifference(object):
1196 def __init__(self, partition, tgt, src=None, check_first_block=False,
1197 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001198 self.tgt = tgt
1199 self.src = src
1200 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001201 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001202
Tao Bao5ece99d2015-05-12 11:42:31 -07001203 # Due to http://b/20939131, check_first_block is disabled temporarily.
1204 assert not self.check_first_block
1205
Tao Baodd2a5892015-03-12 12:32:37 -07001206 if version is None:
1207 version = 1
1208 if OPTIONS.info_dict:
1209 version = max(
1210 int(i) for i in
1211 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1212 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001213
1214 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001215 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001216 tmpdir = tempfile.mkdtemp()
1217 OPTIONS.tempfiles.append(tmpdir)
1218 self.path = os.path.join(tmpdir, partition)
1219 b.Compute(self.path)
1220
1221 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1222
1223 def WriteScript(self, script, output_zip, progress=None):
1224 if not self.src:
1225 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001226 script.Print("Patching %s image unconditionally..." % (self.partition,))
1227 else:
1228 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001229
Dan Albert8b72aef2015-03-23 19:13:21 -07001230 if progress:
1231 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001232 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001233 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001234
1235 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001236 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001237 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001238 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001239 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001240 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1241 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001242 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001243 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1244 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001245 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001246 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001247 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001248 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001249 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001250 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001251 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001252 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001253 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001254
Tao Baodd2a5892015-03-12 12:32:37 -07001255 # When generating incrementals for the system and vendor partitions,
1256 # explicitly check the first block (which contains the superblock) of
1257 # the partition to see if it's what we expect. If this check fails,
1258 # give an explicit log message about the partition having been
1259 # remounted R/W (the most likely explanation) and the need to flash to
1260 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001261 if self.check_first_block:
1262 self._CheckFirstBlock(script)
1263
Tao Baodd2a5892015-03-12 12:32:37 -07001264 # Abort the OTA update. Note that the incremental OTA cannot be applied
1265 # even if it may match the checksum of the target partition.
1266 # a) If version < 3, operations like move and erase will make changes
1267 # unconditionally and damage the partition.
1268 # b) If version >= 3, it won't even reach here.
1269 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1270 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001271
Tao Bao5fcaaef2015-06-01 13:40:49 -07001272 def _WritePostInstallVerifyScript(self, script):
1273 partition = self.partition
1274 script.Print('Verifying the updated %s image...' % (partition,))
1275 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1276 ranges = self.tgt.care_map
1277 ranges_str = ranges.to_string_raw()
1278 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1279 self.device, ranges_str,
1280 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001281
1282 # Bug: 20881595
1283 # Verify that extended blocks are really zeroed out.
1284 if self.tgt.extended:
1285 ranges_str = self.tgt.extended.to_string_raw()
1286 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1287 self.device, ranges_str,
1288 self._HashZeroBlocks(self.tgt.extended.size())))
1289 script.Print('Verified the updated %s image.' % (partition,))
1290 script.AppendExtra(
1291 'else\n'
1292 ' abort("%s partition has unexpected non-zero contents after OTA '
1293 'update");\n'
1294 'endif;' % (partition,))
1295 else:
1296 script.Print('Verified the updated %s image.' % (partition,))
1297
Tao Bao5fcaaef2015-06-01 13:40:49 -07001298 script.AppendExtra(
1299 'else\n'
1300 ' abort("%s partition has unexpected contents after OTA update");\n'
1301 'endif;' % (partition,))
1302
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001303 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001304 ZipWrite(output_zip,
1305 '{}.transfer.list'.format(self.path),
1306 '{}.transfer.list'.format(self.partition))
1307 ZipWrite(output_zip,
1308 '{}.new.dat'.format(self.path),
1309 '{}.new.dat'.format(self.partition))
1310 ZipWrite(output_zip,
1311 '{}.patch.dat'.format(self.path),
1312 '{}.patch.dat'.format(self.partition),
1313 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001314
Dan Albert8e0178d2015-01-27 15:53:15 -08001315 call = ('block_image_update("{device}", '
1316 'package_extract_file("{partition}.transfer.list"), '
1317 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1318 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001319 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001320
Dan Albert8b72aef2015-03-23 19:13:21 -07001321 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001322 data = source.ReadRangeSet(ranges)
1323 ctx = sha1()
1324
1325 for p in data:
1326 ctx.update(p)
1327
1328 return ctx.hexdigest()
1329
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001330 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1331 """Return the hash value for all zero blocks."""
1332 zero_block = '\x00' * 4096
1333 ctx = sha1()
1334 for _ in range(num_blocks):
1335 ctx.update(zero_block)
1336
1337 return ctx.hexdigest()
1338
Tao Bao5ece99d2015-05-12 11:42:31 -07001339 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1340 # remounting R/W. Will change the checking to a finer-grained way to
1341 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001342 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001343 r = rangelib.RangeSet((0, 1))
1344 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001345
1346 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1347 'abort("%s has been remounted R/W; '
1348 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001349 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001350 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001351
1352DataImage = blockimgdiff.DataImage
1353
1354
Doug Zongker96a57e72010-09-26 14:57:41 -07001355# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001356PARTITION_TYPES = {
1357 "yaffs2": "MTD",
1358 "mtd": "MTD",
1359 "ext4": "EMMC",
1360 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001361 "f2fs": "EMMC",
1362 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001363}
Doug Zongker96a57e72010-09-26 14:57:41 -07001364
1365def GetTypeAndDevice(mount_point, info):
1366 fstab = info["fstab"]
1367 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001368 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1369 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001370 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001371 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001372
1373
1374def ParseCertificate(data):
1375 """Parse a PEM-format certificate."""
1376 cert = []
1377 save = False
1378 for line in data.split("\n"):
1379 if "--END CERTIFICATE--" in line:
1380 break
1381 if save:
1382 cert.append(line)
1383 if "--BEGIN CERTIFICATE--" in line:
1384 save = True
1385 cert = "".join(cert).decode('base64')
1386 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001387
Doug Zongker412c02f2014-02-13 10:58:24 -08001388def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1389 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001390 """Generate a binary patch that creates the recovery image starting
1391 with the boot image. (Most of the space in these images is just the
1392 kernel, which is identical for the two, so the resulting patch
1393 should be efficient.) Add it to the output zip, along with a shell
1394 script that is run from init.rc on first boot to actually do the
1395 patching and install the new recovery image.
1396
1397 recovery_img and boot_img should be File objects for the
1398 corresponding images. info should be the dictionary returned by
1399 common.LoadInfoDict() on the input target_files.
1400 """
1401
Doug Zongker412c02f2014-02-13 10:58:24 -08001402 if info_dict is None:
1403 info_dict = OPTIONS.info_dict
1404
Doug Zongkerc9253822014-02-04 12:17:58 -08001405 diff_program = ["imgdiff"]
1406 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1407 if os.path.exists(path):
1408 diff_program.append("-b")
1409 diff_program.append(path)
1410 bonus_args = "-b /system/etc/recovery-resource.dat"
1411 else:
1412 bonus_args = ""
1413
1414 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1415 _, _, patch = d.ComputePatch()
1416 output_sink("recovery-from-boot.p", patch)
1417
Dan Albertebb19aa2015-03-27 19:11:53 -07001418 try:
1419 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1420 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1421 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001422 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001423
1424 sh = """#!/system/bin/sh
1425if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1426 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"
1427else
1428 log -t recovery "Recovery image already installed"
1429fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001430""" % {'boot_size': boot_img.size,
1431 'boot_sha1': boot_img.sha1,
1432 'recovery_size': recovery_img.size,
1433 'recovery_sha1': recovery_img.sha1,
1434 'boot_type': boot_type,
1435 'boot_device': boot_device,
1436 'recovery_type': recovery_type,
1437 'recovery_device': recovery_device,
1438 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001439
1440 # The install script location moved from /system/etc to /system/bin
1441 # in the L release. Parse the init.rc file to find out where the
1442 # target-files expects it to be, and put it there.
1443 sh_location = "etc/install-recovery.sh"
1444 try:
1445 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1446 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001447 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001448 if m:
1449 sh_location = m.group(1)
1450 print "putting script in", sh_location
1451 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001452 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001453 print "failed to read init.rc: %s" % (e,)
1454
1455 output_sink(sh_location, sh)