blob: b61de74bd17eb85a81692da36304c5edebc96baa [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
Ameya Thakure5b5a272013-07-29 17:39:37 -0700151 if "device_type" not in d:
152 d["device_type"] = "MMC"
Doug Zongker37974732010-09-16 17:44:38 -0700153 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800154 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700155 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700156 if not line:
157 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700158 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700159 if not value:
160 continue
Doug Zongker37974732010-09-16 17:44:38 -0700161 if name == "blocksize":
162 d[name] = value
163 else:
164 d[name + "_size"] = value
165 except KeyError:
166 pass
167
168 def makeint(key):
169 if key in d:
170 d[key] = int(d[key], 0)
171
172 makeint("recovery_api_version")
173 makeint("blocksize")
174 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700175 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700176 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700177 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700178 makeint("recovery_size")
179 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800180 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700181
Ameya Thakure5b5a272013-07-29 17:39:37 -0700182 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d["device_type"])
Doug Zongkerc9253822014-02-04 12:17:58 -0800183 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700184 return d
185
Doug Zongkerc9253822014-02-04 12:17:58 -0800186def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700187 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800188 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700189 except KeyError:
190 print "Warning: could not find SYSTEM/build.prop in %s" % zip
191 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700192 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193
Michael Runge6e836112014-04-15 17:40:21 -0700194def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700196 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700197 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700198 if not line or line.startswith("#"):
199 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700200 if "=" in line:
201 name, value = line.split("=", 1)
202 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700203 return d
204
Ameya Thakure5b5a272013-07-29 17:39:37 -0700205def LoadRecoveryFSTab(read_helper, fstab_version, type):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700206 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700207 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700208 self.mount_point = mount_point
209 self.fs_type = fs_type
210 self.device = device
211 self.length = length
212 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700213 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700214
215 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800216 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700217 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800218 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700219 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700220
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800221 if fstab_version == 1:
222 d = {}
223 for line in data.split("\n"):
224 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700225 if not line or line.startswith("#"):
226 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700228 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800229 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800230 options = None
231 if len(pieces) >= 4:
232 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700233 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800234 if len(pieces) >= 5:
235 options = pieces[4]
236 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800239 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700240 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700241
Dan Albert8b72aef2015-03-23 19:13:21 -0700242 mount_point = pieces[0]
243 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 if options:
245 options = options.split(",")
246 for i in options:
247 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700250 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800251
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
253 device=pieces[2], length=length,
254 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255
256 elif fstab_version == 2:
257 d = {}
258 for line in data.split("\n"):
259 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700260 if not line or line.startswith("#"):
261 continue
Tao Bao548eb762015-06-10 12:32:41 -0700262 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 pieces = line.split()
264 if len(pieces) != 5:
265 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
266
267 # Ignore entries that are managed by vold
268 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700269 if "voldmanaged=" in options:
270 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800271
272 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800274 options = options.split(",")
275 for i in options:
276 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800278 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800279 # Ignore all unknown options in the unified fstab
280 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800281
Tao Bao548eb762015-06-10 12:32:41 -0700282 mount_flags = pieces[3]
283 # Honor the SELinux context if present.
284 context = None
285 for i in mount_flags.split(","):
286 if i.startswith("context="):
287 context = i
288
Dan Albert8b72aef2015-03-23 19:13:21 -0700289 mount_point = pieces[1]
290 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700291 device=pieces[0], length=length,
292 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800293
294 else:
295 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
296
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700297 return d
298
299
Doug Zongker37974732010-09-16 17:44:38 -0700300def DumpInfoDict(d):
301 for k, v in sorted(d.items()):
302 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700303
Dan Albert8b72aef2015-03-23 19:13:21 -0700304
Doug Zongkerd5131602012-08-02 14:46:42 -0700305def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700306 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700307 'sourcedir'), and turn them into a boot image. Return the image
308 data, or None if sourcedir does not appear to contains files for
309 building the requested image."""
310
311 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
312 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
313 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700314
Doug Zongkerd5131602012-08-02 14:46:42 -0700315 if info_dict is None:
316 info_dict = OPTIONS.info_dict
317
Doug Zongkereef39442009-04-02 12:14:19 -0700318 ramdisk_img = tempfile.NamedTemporaryFile()
319 img = tempfile.NamedTemporaryFile()
320
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700321 if os.access(fs_config_file, os.F_OK):
322 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
323 else:
324 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
325 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700326 p2 = Run(["minigzip"],
327 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700328
329 p2.wait()
330 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700331 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
332 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700333
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000334 """check if uboot is requested"""
335 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200336 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000337 cmd = ["mkimage"]
338 for argument in open(fn).read().rstrip("\n").split(" "):
339 cmd.append(argument)
340 cmd.append("-d")
341 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
342 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200343
Tao Baod95e9fd2015-03-29 23:07:41 -0700344 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000345 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
346 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
347 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700348
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000349 fn = os.path.join(sourcedir, "second")
350 if os.access(fn, os.F_OK):
351 cmd.append("--second")
352 cmd.append(fn)
353
354 fn = os.path.join(sourcedir, "cmdline")
355 if os.access(fn, os.F_OK):
356 cmd.append("--cmdline")
357 cmd.append(open(fn).read().rstrip("\n"))
358
359 fn = os.path.join(sourcedir, "base")
360 if os.access(fn, os.F_OK):
361 cmd.append("--base")
362 cmd.append(open(fn).read().rstrip("\n"))
363
364 fn = os.path.join(sourcedir, "tagsaddr")
365 if os.access(fn, os.F_OK):
366 cmd.append("--tags-addr")
367 cmd.append(open(fn).read().rstrip("\n"))
368
Ameya Thakure5b5a272013-07-29 17:39:37 -0700369 fn = os.path.join(sourcedir, "tags_offset")
370 if os.access(fn, os.F_OK):
371 cmd.append("--tags_offset")
372 cmd.append(open(fn).read().rstrip("\n"))
373
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000374 fn = os.path.join(sourcedir, "ramdisk_offset")
375 if os.access(fn, os.F_OK):
376 cmd.append("--ramdisk_offset")
377 cmd.append(open(fn).read().rstrip("\n"))
378
379 fn = os.path.join(sourcedir, "dt_args")
380 if os.access(fn, os.F_OK):
381 cmd.append("--dt")
382 cmd.append(open(fn).read().rstrip("\n"))
383
384 fn = os.path.join(sourcedir, "pagesize")
385 if os.access(fn, os.F_OK):
386 cmd.append("--pagesize")
387 cmd.append(open(fn).read().rstrip("\n"))
388
389 args = info_dict.get("mkbootimg_args", None)
390 if args and args.strip():
391 cmd.extend(shlex.split(args))
392
393 img_unsigned = None
394 if info_dict.get("vboot", None):
395 img_unsigned = tempfile.NamedTemporaryFile()
396 cmd.extend(["--ramdisk", ramdisk_img.name,
397 "--output", img_unsigned.name])
398 else:
399 cmd.extend(["--ramdisk", ramdisk_img.name,
400 "--output", img.name])
401
Doug Zongker38a649f2009-06-17 09:07:09 -0700402 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700403 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700404 assert p.returncode == 0, "mkbootimg of %s image failed" % (
405 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700406
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100407 if (info_dict.get("boot_signer", None) == "true" and
408 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700409 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700410 cmd = [OPTIONS.boot_signer_path]
411 cmd.extend(OPTIONS.boot_signer_args)
412 cmd.extend([path, img.name,
413 info_dict["verity_key"] + ".pk8",
414 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700415 p = Run(cmd, stdout=subprocess.PIPE)
416 p.communicate()
417 assert p.returncode == 0, "boot_signer of %s image failed" % path
418
Tao Baod95e9fd2015-03-29 23:07:41 -0700419 # Sign the image if vboot is non-empty.
420 elif info_dict.get("vboot", None):
421 path = "/" + os.path.basename(sourcedir).lower()
422 img_keyblock = tempfile.NamedTemporaryFile()
423 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
424 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
425 info_dict["vboot_key"] + ".vbprivk", img_keyblock.name,
426 img.name]
427 p = Run(cmd, stdout=subprocess.PIPE)
428 p.communicate()
429 assert p.returncode == 0, "vboot_signer of %s image failed" % path
430
Tao Baof3282b42015-04-01 11:21:55 -0700431 # Clean up the temp files.
432 img_unsigned.close()
433 img_keyblock.close()
434
Doug Zongkereef39442009-04-02 12:14:19 -0700435 img.seek(os.SEEK_SET, 0)
436 data = img.read()
437
438 ramdisk_img.close()
439 img.close()
440
441 return data
442
443
Doug Zongkerd5131602012-08-02 14:46:42 -0700444def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
445 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800446 """Return a File object (with name 'name') with the desired bootable
447 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700448 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
449 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800450 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700451
Doug Zongker55d93282011-01-25 17:03:34 -0800452 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
453 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700454 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800455 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700456
457 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
458 if os.path.exists(prebuilt_path):
459 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
460 return File.FromLocalFile(name, prebuilt_path)
461
462 print "building image from target_files %s..." % (tree_subdir,)
463 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
464 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
465 os.path.join(unpack_dir, fs_config),
466 info_dict)
467 if data:
468 return File(name, data)
469 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800470
Doug Zongkereef39442009-04-02 12:14:19 -0700471
Doug Zongker75f17362009-12-08 13:46:44 -0800472def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800473 """Unzip the given archive into a temporary directory and return the name.
474
475 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
476 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
477
478 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
479 main file), open for reading.
480 """
Doug Zongkereef39442009-04-02 12:14:19 -0700481
482 tmp = tempfile.mkdtemp(prefix="targetfiles-")
483 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800484
485 def unzip_to_dir(filename, dirname):
Dan Pasanen17341b12014-12-21 23:18:16 -0600486 cmd = ["rm", "-rf", dirname + filename, "targetfiles-*"]
Doug Zongker55d93282011-01-25 17:03:34 -0800487 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
488 if pattern is not None:
489 cmd.append(pattern)
490 p = Run(cmd, stdout=subprocess.PIPE)
491 p.communicate()
492 if p.returncode != 0:
493 raise ExternalError("failed to unzip input target-files \"%s\"" %
494 (filename,))
495
496 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
497 if m:
498 unzip_to_dir(m.group(1), tmp)
499 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
500 filename = m.group(1)
501 else:
502 unzip_to_dir(filename, tmp)
503
504 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700505
506
507def GetKeyPasswords(keylist):
508 """Given a list of keys, prompt the user to enter passwords for
509 those which require them. Return a {key: password} dict. password
510 will be None if the key has no password."""
511
Doug Zongker8ce7c252009-05-22 13:34:54 -0700512 no_passwords = []
513 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700514 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700515 devnull = open("/dev/null", "w+b")
516 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800517 # We don't need a password for things that aren't really keys.
518 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700519 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700520 continue
521
T.R. Fullhart37e10522013-03-18 10:31:26 -0700522 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700523 "-inform", "DER", "-nocrypt"],
524 stdin=devnull.fileno(),
525 stdout=devnull.fileno(),
526 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700527 p.communicate()
528 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700529 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700530 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700531 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700532 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
533 "-inform", "DER", "-passin", "pass:"],
534 stdin=devnull.fileno(),
535 stdout=devnull.fileno(),
536 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700537 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700538 if p.returncode == 0:
539 # Encrypted key with empty string as password.
540 key_passwords[k] = ''
541 elif stderr.startswith('Error decrypting key'):
542 # Definitely encrypted key.
543 # It would have said "Error reading key" if it didn't parse correctly.
544 need_passwords.append(k)
545 else:
546 # Potentially, a type of key that openssl doesn't understand.
547 # We'll let the routines in signapk.jar handle it.
548 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700549 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700550
T.R. Fullhart37e10522013-03-18 10:31:26 -0700551 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700552 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700553 return key_passwords
554
555
Doug Zongker951495f2009-08-14 12:44:19 -0700556def SignFile(input_name, output_name, key, password, align=None,
557 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700558 """Sign the input_name zip/jar/apk, producing output_name. Use the
559 given key and password (the latter may be None if the key does not
560 have a password.
561
562 If align is an integer > 1, zipalign is run to align stored files in
563 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700564
565 If whole_file is true, use the "-w" option to SignApk to embed a
566 signature that covers the whole file in the archive comment of the
567 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700568 """
Doug Zongker951495f2009-08-14 12:44:19 -0700569
Doug Zongkereef39442009-04-02 12:14:19 -0700570 if align == 0 or align == 1:
571 align = None
572
573 if align:
574 temp = tempfile.NamedTemporaryFile()
575 sign_name = temp.name
576 else:
577 sign_name = output_name
578
Baligh Uddin339ee492014-09-05 11:18:07 -0700579 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700580 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
581 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700582 if whole_file:
583 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700584 cmd.extend([key + OPTIONS.public_key_suffix,
585 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700586 input_name, sign_name])
587
588 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700589 if password is not None:
590 password += "\n"
591 p.communicate(password)
592 if p.returncode != 0:
593 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
594
595 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700596 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700597 p.communicate()
598 if p.returncode != 0:
599 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
600 temp.close()
601
602
Doug Zongker37974732010-09-16 17:44:38 -0700603def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700604 """Check the data string passed against the max size limit, if
605 any, for the given target. Raise exception if the data is too big.
606 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700607
Dan Albert8b72aef2015-03-23 19:13:21 -0700608 if target.endswith(".img"):
609 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700610 mount_point = "/" + target
611
Ying Wangf8824af2014-06-03 14:07:27 -0700612 fs_type = None
613 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700614 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700615 if mount_point == "/userdata":
616 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700617 p = info_dict["fstab"][mount_point]
618 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800619 device = p.device
620 if "/" in device:
621 device = device[device.rfind("/")+1:]
622 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700623 if not fs_type or not limit:
624 return
Doug Zongkereef39442009-04-02 12:14:19 -0700625
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700626 if fs_type == "yaffs2":
627 # image size should be increased by 1/64th to account for the
628 # spare area (64 bytes per 2k page)
629 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800630 size = len(data)
631 pct = float(size) * 100.0 / limit
632 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
633 if pct >= 99.0:
634 raise ExternalError(msg)
635 elif pct >= 95.0:
636 print
637 print " WARNING: ", msg
638 print
639 elif OPTIONS.verbose:
640 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700641
642
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800643def ReadApkCerts(tf_zip):
644 """Given a target_files ZipFile, parse the META/apkcerts.txt file
645 and return a {package: cert} dict."""
646 certmap = {}
647 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
648 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700649 if not line:
650 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800651 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
652 r'private_key="(.*)"$', line)
653 if m:
654 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700655 public_key_suffix_len = len(OPTIONS.public_key_suffix)
656 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800657 if cert in SPECIAL_CERT_STRINGS and not privkey:
658 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700659 elif (cert.endswith(OPTIONS.public_key_suffix) and
660 privkey.endswith(OPTIONS.private_key_suffix) and
661 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
662 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800663 else:
664 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
665 return certmap
666
667
Doug Zongkereef39442009-04-02 12:14:19 -0700668COMMON_DOCSTRING = """
669 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700670 Prepend <dir>/bin to the list of places to search for binaries
671 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700672
Doug Zongker05d3dea2009-06-22 11:32:31 -0700673 -s (--device_specific) <file>
674 Path to the python module containing device-specific
675 releasetools code.
676
Doug Zongker8bec09e2009-11-30 15:37:14 -0800677 -x (--extra) <key=value>
678 Add a key/value pair to the 'extras' dict, which device-specific
679 extension code may look at.
680
Doug Zongkereef39442009-04-02 12:14:19 -0700681 -v (--verbose)
682 Show command lines being executed.
683
684 -h (--help)
685 Display this usage message and exit.
686"""
687
688def Usage(docstring):
689 print docstring.rstrip("\n")
690 print COMMON_DOCSTRING
691
692
693def ParseOptions(argv,
694 docstring,
695 extra_opts="", extra_long_opts=(),
696 extra_option_handler=None):
697 """Parse the options in argv and return any arguments that aren't
698 flags. docstring is the calling module's docstring, to be displayed
699 for errors and -h. extra_opts and extra_long_opts are for flags
700 defined by the caller, which are processed by passing them to
701 extra_option_handler."""
702
703 try:
704 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800705 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700706 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700707 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700708 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
709 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800710 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700711 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700712 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700713 Usage(docstring)
714 print "**", str(err), "**"
715 sys.exit(2)
716
Doug Zongkereef39442009-04-02 12:14:19 -0700717 for o, a in opts:
718 if o in ("-h", "--help"):
719 Usage(docstring)
720 sys.exit()
721 elif o in ("-v", "--verbose"):
722 OPTIONS.verbose = True
723 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700724 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700725 elif o in ("--signapk_path",):
726 OPTIONS.signapk_path = a
727 elif o in ("--extra_signapk_args",):
728 OPTIONS.extra_signapk_args = shlex.split(a)
729 elif o in ("--java_path",):
730 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700731 elif o in ("--java_args",):
732 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 elif o in ("--public_key_suffix",):
734 OPTIONS.public_key_suffix = a
735 elif o in ("--private_key_suffix",):
736 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800737 elif o in ("--boot_signer_path",):
738 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700739 elif o in ("--boot_signer_args",):
740 OPTIONS.boot_signer_args = shlex.split(a)
741 elif o in ("--verity_signer_path",):
742 OPTIONS.verity_signer_path = a
743 elif o in ("--verity_signer_args",):
744 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700745 elif o in ("-s", "--device_specific"):
746 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800747 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800748 key, value = a.split("=", 1)
749 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700750 else:
751 if extra_option_handler is None or not extra_option_handler(o, a):
752 assert False, "unknown option \"%s\"" % (o,)
753
Doug Zongker85448772014-09-09 14:59:20 -0700754 if OPTIONS.search_path:
755 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
756 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700757
758 return args
759
760
Doug Zongkerfc44a512014-08-26 13:10:25 -0700761def MakeTempFile(prefix=None, suffix=None):
762 """Make a temp file and add it to the list of things to be deleted
763 when Cleanup() is called. Return the filename."""
764 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
765 os.close(fd)
766 OPTIONS.tempfiles.append(fn)
767 return fn
768
769
Doug Zongkereef39442009-04-02 12:14:19 -0700770def Cleanup():
771 for i in OPTIONS.tempfiles:
772 if os.path.isdir(i):
773 shutil.rmtree(i)
774 else:
775 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700776
777
778class PasswordManager(object):
779 def __init__(self):
780 self.editor = os.getenv("EDITOR", None)
781 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
782
783 def GetPasswords(self, items):
784 """Get passwords corresponding to each string in 'items',
785 returning a dict. (The dict may have keys in addition to the
786 values in 'items'.)
787
788 Uses the passwords in $ANDROID_PW_FILE if available, letting the
789 user edit that file to add more needed passwords. If no editor is
790 available, or $ANDROID_PW_FILE isn't define, prompts the user
791 interactively in the ordinary way.
792 """
793
794 current = self.ReadFile()
795
796 first = True
797 while True:
798 missing = []
799 for i in items:
800 if i not in current or not current[i]:
801 missing.append(i)
802 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700803 if not missing:
804 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700805
806 for i in missing:
807 current[i] = ""
808
809 if not first:
810 print "key file %s still missing some passwords." % (self.pwfile,)
811 answer = raw_input("try to edit again? [y]> ").strip()
812 if answer and answer[0] not in 'yY':
813 raise RuntimeError("key passwords unavailable")
814 first = False
815
816 current = self.UpdateAndReadFile(current)
817
Dan Albert8b72aef2015-03-23 19:13:21 -0700818 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700819 """Prompt the user to enter a value (password) for each key in
820 'current' whose value is fales. Returns a new dict with all the
821 values.
822 """
823 result = {}
824 for k, v in sorted(current.iteritems()):
825 if v:
826 result[k] = v
827 else:
828 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700829 result[k] = getpass.getpass(
830 "Enter password for %s key> " % k).strip()
831 if result[k]:
832 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700833 return result
834
835 def UpdateAndReadFile(self, current):
836 if not self.editor or not self.pwfile:
837 return self.PromptResult(current)
838
839 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700840 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700841 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
842 f.write("# (Additional spaces are harmless.)\n\n")
843
844 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700845 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
846 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700847 f.write("[[[ %s ]]] %s\n" % (v, k))
848 if not v and first_line is None:
849 # position cursor on first line with no password.
850 first_line = i + 4
851 f.close()
852
853 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
854 _, _ = p.communicate()
855
856 return self.ReadFile()
857
858 def ReadFile(self):
859 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700860 if self.pwfile is None:
861 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700862 try:
863 f = open(self.pwfile, "r")
864 for line in f:
865 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 if not line or line[0] == '#':
867 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
869 if not m:
870 print "failed to parse password file: ", line
871 else:
872 result[m.group(2)] = m.group(1)
873 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700875 if e.errno != errno.ENOENT:
876 print "error reading password file: ", str(e)
877 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700878
879
Dan Albert8e0178d2015-01-27 15:53:15 -0800880def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
881 compress_type=None):
882 import datetime
883
884 # http://b/18015246
885 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
886 # for files larger than 2GiB. We can work around this by adjusting their
887 # limit. Note that `zipfile.writestr()` will not work for strings larger than
888 # 2GiB. The Python interpreter sometimes rejects strings that large (though
889 # it isn't clear to me exactly what circumstances cause this).
890 # `zipfile.write()` must be used directly to work around this.
891 #
892 # This mess can be avoided if we port to python3.
893 saved_zip64_limit = zipfile.ZIP64_LIMIT
894 zipfile.ZIP64_LIMIT = (1 << 32) - 1
895
896 if compress_type is None:
897 compress_type = zip_file.compression
898 if arcname is None:
899 arcname = filename
900
901 saved_stat = os.stat(filename)
902
903 try:
904 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
905 # file to be zipped and reset it when we're done.
906 os.chmod(filename, perms)
907
908 # Use a fixed timestamp so the output is repeatable.
909 epoch = datetime.datetime.fromtimestamp(0)
910 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
911 os.utime(filename, (timestamp, timestamp))
912
913 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
914 finally:
915 os.chmod(filename, saved_stat.st_mode)
916 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
917 zipfile.ZIP64_LIMIT = saved_zip64_limit
918
919
Tao Bao58c1b962015-05-20 09:32:18 -0700920def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700921 compress_type=None):
922 """Wrap zipfile.writestr() function to work around the zip64 limit.
923
924 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
925 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
926 when calling crc32(bytes).
927
928 But it still works fine to write a shorter string into a large zip file.
929 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
930 when we know the string won't be too long.
931 """
932
933 saved_zip64_limit = zipfile.ZIP64_LIMIT
934 zipfile.ZIP64_LIMIT = (1 << 32) - 1
935
936 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
937 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700938 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700939 if perms is None:
940 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800941 else:
Tao Baof3282b42015-04-01 11:21:55 -0700942 zinfo = zinfo_or_arcname
943
944 # If compress_type is given, it overrides the value in zinfo.
945 if compress_type is not None:
946 zinfo.compress_type = compress_type
947
Tao Bao58c1b962015-05-20 09:32:18 -0700948 # If perms is given, it has a priority.
949 if perms is not None:
950 zinfo.external_attr = perms << 16
951
Tao Baof3282b42015-04-01 11:21:55 -0700952 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700953 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
954
Dan Albert8b72aef2015-03-23 19:13:21 -0700955 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700956 zipfile.ZIP64_LIMIT = saved_zip64_limit
957
958
959def ZipClose(zip_file):
960 # http://b/18015246
961 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
962 # central directory.
963 saved_zip64_limit = zipfile.ZIP64_LIMIT
964 zipfile.ZIP64_LIMIT = (1 << 32) - 1
965
966 zip_file.close()
967
968 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700969
970
971class DeviceSpecificParams(object):
972 module = None
973 def __init__(self, **kwargs):
974 """Keyword arguments to the constructor become attributes of this
975 object, which is passed to all functions in the device-specific
976 module."""
977 for k, v in kwargs.iteritems():
978 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800979 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700980
981 if self.module is None:
982 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700983 if not path:
984 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700985 try:
986 if os.path.isdir(path):
987 info = imp.find_module("releasetools", [path])
988 else:
989 d, f = os.path.split(path)
990 b, x = os.path.splitext(f)
991 if x == ".py":
992 f = b
993 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800994 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700995 self.module = imp.load_module("device_specific", *info)
996 except ImportError:
997 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700998
999 def _DoCall(self, function_name, *args, **kwargs):
1000 """Call the named function in the device-specific module, passing
1001 the given args and kwargs. The first argument to the call will be
1002 the DeviceSpecific object itself. If there is no module, or the
1003 module does not define the function, return the value of the
1004 'default' kwarg (which itself defaults to None)."""
1005 if self.module is None or not hasattr(self.module, function_name):
1006 return kwargs.get("default", None)
1007 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1008
1009 def FullOTA_Assertions(self):
1010 """Called after emitting the block of assertions at the top of a
1011 full OTA package. Implementations can add whatever additional
1012 assertions they like."""
1013 return self._DoCall("FullOTA_Assertions")
1014
Doug Zongkere5ff5902012-01-17 10:55:37 -08001015 def FullOTA_InstallBegin(self):
1016 """Called at the start of full OTA installation."""
1017 return self._DoCall("FullOTA_InstallBegin")
1018
Doug Zongker05d3dea2009-06-22 11:32:31 -07001019 def FullOTA_InstallEnd(self):
1020 """Called at the end of full OTA installation; typically this is
1021 used to install the image for the device's baseband processor."""
1022 return self._DoCall("FullOTA_InstallEnd")
1023
M1chad27ccf72014-11-25 15:30:48 +01001024 def FullOTA_PostValidate(self):
1025 """Called after installing and validating /system; typically this is
1026 used to resize the system partition after a block based installation."""
1027 return self._DoCall("FullOTA_PostValidate")
1028
Doug Zongker05d3dea2009-06-22 11:32:31 -07001029 def IncrementalOTA_Assertions(self):
1030 """Called after emitting the block of assertions at the top of an
1031 incremental OTA package. Implementations can add whatever
1032 additional assertions they like."""
1033 return self._DoCall("IncrementalOTA_Assertions")
1034
Doug Zongkere5ff5902012-01-17 10:55:37 -08001035 def IncrementalOTA_VerifyBegin(self):
1036 """Called at the start of the verification phase of incremental
1037 OTA installation; additional checks can be placed here to abort
1038 the script before any changes are made."""
1039 return self._DoCall("IncrementalOTA_VerifyBegin")
1040
Doug Zongker05d3dea2009-06-22 11:32:31 -07001041 def IncrementalOTA_VerifyEnd(self):
1042 """Called at the end of the verification phase of incremental OTA
1043 installation; additional checks can be placed here to abort the
1044 script before any changes are made."""
1045 return self._DoCall("IncrementalOTA_VerifyEnd")
1046
Doug Zongkere5ff5902012-01-17 10:55:37 -08001047 def IncrementalOTA_InstallBegin(self):
1048 """Called at the start of incremental OTA installation (after
1049 verification is complete)."""
1050 return self._DoCall("IncrementalOTA_InstallBegin")
1051
Doug Zongker05d3dea2009-06-22 11:32:31 -07001052 def IncrementalOTA_InstallEnd(self):
1053 """Called at the end of incremental OTA installation; typically
1054 this is used to install the image for the device's baseband
1055 processor."""
1056 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001057
1058class File(object):
1059 def __init__(self, name, data):
1060 self.name = name
1061 self.data = data
1062 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001063 self.sha1 = sha1(data).hexdigest()
1064
1065 @classmethod
1066 def FromLocalFile(cls, name, diskname):
1067 f = open(diskname, "rb")
1068 data = f.read()
1069 f.close()
1070 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001071
1072 def WriteToTemp(self):
1073 t = tempfile.NamedTemporaryFile()
1074 t.write(self.data)
1075 t.flush()
1076 return t
1077
Geremy Condra36bd3652014-02-06 19:45:10 -08001078 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001079 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001080
1081DIFF_PROGRAM_BY_EXT = {
1082 ".gz" : "imgdiff",
1083 ".zip" : ["imgdiff", "-z"],
1084 ".jar" : ["imgdiff", "-z"],
1085 ".apk" : ["imgdiff", "-z"],
1086 ".img" : "imgdiff",
1087 }
1088
1089class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001090 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001091 self.tf = tf
1092 self.sf = sf
1093 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001094 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001095
1096 def ComputePatch(self):
1097 """Compute the patch (as a string of data) needed to turn sf into
1098 tf. Returns the same tuple as GetPatch()."""
1099
1100 tf = self.tf
1101 sf = self.sf
1102
Doug Zongker24cd2802012-08-14 16:36:15 -07001103 if self.diff_program:
1104 diff_program = self.diff_program
1105 else:
1106 ext = os.path.splitext(tf.name)[1]
1107 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001108
1109 ttemp = tf.WriteToTemp()
1110 stemp = sf.WriteToTemp()
1111
1112 ext = os.path.splitext(tf.name)[1]
1113
1114 try:
1115 ptemp = tempfile.NamedTemporaryFile()
1116 if isinstance(diff_program, list):
1117 cmd = copy.copy(diff_program)
1118 else:
1119 cmd = [diff_program]
1120 cmd.append(stemp.name)
1121 cmd.append(ttemp.name)
1122 cmd.append(ptemp.name)
1123 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001124 err = []
1125 def run():
1126 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001127 if e:
1128 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001129 th = threading.Thread(target=run)
1130 th.start()
1131 th.join(timeout=300) # 5 mins
1132 if th.is_alive():
1133 print "WARNING: diff command timed out"
1134 p.terminate()
1135 th.join(5)
1136 if th.is_alive():
1137 p.kill()
1138 th.join()
1139
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001140 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001141 print "WARNING: failure running %s:\n%s\n" % (
1142 diff_program, "".join(err))
1143 self.patch = None
1144 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001145 diff = ptemp.read()
1146 finally:
1147 ptemp.close()
1148 stemp.close()
1149 ttemp.close()
1150
1151 self.patch = diff
1152 return self.tf, self.sf, self.patch
1153
1154
1155 def GetPatch(self):
1156 """Return a tuple (target_file, source_file, patch_data).
1157 patch_data may be None if ComputePatch hasn't been called, or if
1158 computing the patch failed."""
1159 return self.tf, self.sf, self.patch
1160
1161
1162def ComputeDifferences(diffs):
1163 """Call ComputePatch on all the Difference objects in 'diffs'."""
1164 print len(diffs), "diffs to compute"
1165
1166 # Do the largest files first, to try and reduce the long-pole effect.
1167 by_size = [(i.tf.size, i) for i in diffs]
1168 by_size.sort(reverse=True)
1169 by_size = [i[1] for i in by_size]
1170
1171 lock = threading.Lock()
1172 diff_iter = iter(by_size) # accessed under lock
1173
1174 def worker():
1175 try:
1176 lock.acquire()
1177 for d in diff_iter:
1178 lock.release()
1179 start = time.time()
1180 d.ComputePatch()
1181 dur = time.time() - start
1182 lock.acquire()
1183
1184 tf, sf, patch = d.GetPatch()
1185 if sf.name == tf.name:
1186 name = tf.name
1187 else:
1188 name = "%s (%s)" % (tf.name, sf.name)
1189 if patch is None:
1190 print "patching failed! %s" % (name,)
1191 else:
1192 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1193 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1194 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001195 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001196 print e
1197 raise
1198
1199 # start worker threads; wait for them all to finish.
1200 threads = [threading.Thread(target=worker)
1201 for i in range(OPTIONS.worker_threads)]
1202 for th in threads:
1203 th.start()
1204 while threads:
1205 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001206
1207
Dan Albert8b72aef2015-03-23 19:13:21 -07001208class BlockDifference(object):
1209 def __init__(self, partition, tgt, src=None, check_first_block=False,
1210 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001211 self.tgt = tgt
1212 self.src = src
1213 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001214 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001215
Tao Bao5ece99d2015-05-12 11:42:31 -07001216 # Due to http://b/20939131, check_first_block is disabled temporarily.
1217 assert not self.check_first_block
1218
Tao Baodd2a5892015-03-12 12:32:37 -07001219 if version is None:
1220 version = 1
1221 if OPTIONS.info_dict:
1222 version = max(
1223 int(i) for i in
1224 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1225 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001226
1227 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001228 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001229 tmpdir = tempfile.mkdtemp()
1230 OPTIONS.tempfiles.append(tmpdir)
1231 self.path = os.path.join(tmpdir, partition)
1232 b.Compute(self.path)
1233
1234 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1235
1236 def WriteScript(self, script, output_zip, progress=None):
1237 if not self.src:
1238 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001239 script.Print("Patching %s image unconditionally..." % (self.partition,))
1240 else:
1241 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001242
Dan Albert8b72aef2015-03-23 19:13:21 -07001243 if progress:
1244 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001245 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001246 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001247
1248 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001249 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001250 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001251 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001252 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001253 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1254 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001255 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001256 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1257 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001258 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001259 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001260 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001261 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001262 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001263 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001264 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001265 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001266 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001267
Tao Baodd2a5892015-03-12 12:32:37 -07001268 # When generating incrementals for the system and vendor partitions,
1269 # explicitly check the first block (which contains the superblock) of
1270 # the partition to see if it's what we expect. If this check fails,
1271 # give an explicit log message about the partition having been
1272 # remounted R/W (the most likely explanation) and the need to flash to
1273 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001274 if self.check_first_block:
1275 self._CheckFirstBlock(script)
1276
Tao Baodd2a5892015-03-12 12:32:37 -07001277 # Abort the OTA update. Note that the incremental OTA cannot be applied
1278 # even if it may match the checksum of the target partition.
1279 # a) If version < 3, operations like move and erase will make changes
1280 # unconditionally and damage the partition.
1281 # b) If version >= 3, it won't even reach here.
1282 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1283 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001284
Tao Bao5fcaaef2015-06-01 13:40:49 -07001285 def _WritePostInstallVerifyScript(self, script):
1286 partition = self.partition
1287 script.Print('Verifying the updated %s image...' % (partition,))
1288 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1289 ranges = self.tgt.care_map
1290 ranges_str = ranges.to_string_raw()
1291 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1292 self.device, ranges_str,
1293 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001294
1295 # Bug: 20881595
1296 # Verify that extended blocks are really zeroed out.
1297 if self.tgt.extended:
1298 ranges_str = self.tgt.extended.to_string_raw()
1299 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1300 self.device, ranges_str,
1301 self._HashZeroBlocks(self.tgt.extended.size())))
1302 script.Print('Verified the updated %s image.' % (partition,))
1303 script.AppendExtra(
1304 'else\n'
1305 ' abort("%s partition has unexpected non-zero contents after OTA '
1306 'update");\n'
1307 'endif;' % (partition,))
1308 else:
1309 script.Print('Verified the updated %s image.' % (partition,))
1310
Tao Bao5fcaaef2015-06-01 13:40:49 -07001311 script.AppendExtra(
1312 'else\n'
1313 ' abort("%s partition has unexpected contents after OTA update");\n'
1314 'endif;' % (partition,))
1315
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001316 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001317 ZipWrite(output_zip,
1318 '{}.transfer.list'.format(self.path),
1319 '{}.transfer.list'.format(self.partition))
1320 ZipWrite(output_zip,
1321 '{}.new.dat'.format(self.path),
1322 '{}.new.dat'.format(self.partition))
1323 ZipWrite(output_zip,
1324 '{}.patch.dat'.format(self.path),
1325 '{}.patch.dat'.format(self.partition),
1326 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001327
Dan Albert8e0178d2015-01-27 15:53:15 -08001328 call = ('block_image_update("{device}", '
1329 'package_extract_file("{partition}.transfer.list"), '
1330 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1331 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001332 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001333
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001335 data = source.ReadRangeSet(ranges)
1336 ctx = sha1()
1337
1338 for p in data:
1339 ctx.update(p)
1340
1341 return ctx.hexdigest()
1342
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001343 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1344 """Return the hash value for all zero blocks."""
1345 zero_block = '\x00' * 4096
1346 ctx = sha1()
1347 for _ in range(num_blocks):
1348 ctx.update(zero_block)
1349
1350 return ctx.hexdigest()
1351
Tao Bao5ece99d2015-05-12 11:42:31 -07001352 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1353 # remounting R/W. Will change the checking to a finer-grained way to
1354 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001355 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001356 r = rangelib.RangeSet((0, 1))
1357 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001358
1359 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1360 'abort("%s has been remounted R/W; '
1361 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001362 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001363 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001364
1365DataImage = blockimgdiff.DataImage
1366
1367
Doug Zongker96a57e72010-09-26 14:57:41 -07001368# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001369PARTITION_TYPES = {
1370 "yaffs2": "MTD",
1371 "mtd": "MTD",
1372 "ext4": "EMMC",
1373 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001374 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001375 "squashfs": "EMMC",
1376 "ext2": "EMMC",
1377 "ext3": "EMMC",
1378 "vfat": "EMMC" }
Dan Albert8b72aef2015-03-23 19:13:21 -07001379}
Doug Zongker96a57e72010-09-26 14:57:41 -07001380
1381def GetTypeAndDevice(mount_point, info):
1382 fstab = info["fstab"]
1383 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001384 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1385 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001386 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001387 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001388
1389
1390def ParseCertificate(data):
1391 """Parse a PEM-format certificate."""
1392 cert = []
1393 save = False
1394 for line in data.split("\n"):
1395 if "--END CERTIFICATE--" in line:
1396 break
1397 if save:
1398 cert.append(line)
1399 if "--BEGIN CERTIFICATE--" in line:
1400 save = True
1401 cert = "".join(cert).decode('base64')
1402 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001403
Doug Zongker412c02f2014-02-13 10:58:24 -08001404def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1405 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001406 """Generate a binary patch that creates the recovery image starting
1407 with the boot image. (Most of the space in these images is just the
1408 kernel, which is identical for the two, so the resulting patch
1409 should be efficient.) Add it to the output zip, along with a shell
1410 script that is run from init.rc on first boot to actually do the
1411 patching and install the new recovery image.
1412
1413 recovery_img and boot_img should be File objects for the
1414 corresponding images. info should be the dictionary returned by
1415 common.LoadInfoDict() on the input target_files.
1416 """
1417
Doug Zongker412c02f2014-02-13 10:58:24 -08001418 if info_dict is None:
1419 info_dict = OPTIONS.info_dict
1420
Doug Zongkerc9253822014-02-04 12:17:58 -08001421 diff_program = ["imgdiff"]
1422 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1423 if os.path.exists(path):
1424 diff_program.append("-b")
1425 diff_program.append(path)
1426 bonus_args = "-b /system/etc/recovery-resource.dat"
1427 else:
1428 bonus_args = ""
1429
1430 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1431 _, _, patch = d.ComputePatch()
1432 output_sink("recovery-from-boot.p", patch)
1433
Dan Albertebb19aa2015-03-27 19:11:53 -07001434 try:
1435 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1436 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1437 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001438 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001439
1440 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001441if [ -f /system/etc/recovery-transform.sh ]; then
1442 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1443fi
1444
Doug Zongkerc9253822014-02-04 12:17:58 -08001445if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1446 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"
1447else
1448 log -t recovery "Recovery image already installed"
1449fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001450""" % {'boot_size': boot_img.size,
1451 'boot_sha1': boot_img.sha1,
1452 'recovery_size': recovery_img.size,
1453 'recovery_sha1': recovery_img.sha1,
1454 'boot_type': boot_type,
1455 'boot_device': boot_device,
1456 'recovery_type': recovery_type,
1457 'recovery_device': recovery_device,
1458 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001459
1460 # The install script location moved from /system/etc to /system/bin
1461 # in the L release. Parse the init.rc file to find out where the
1462 # target-files expects it to be, and put it there.
1463 sh_location = "etc/install-recovery.sh"
1464 try:
1465 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1466 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001467 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001468 if m:
1469 sh_location = m.group(1)
1470 print "putting script in", sh_location
1471 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001472 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001473 print "failed to read init.rc: %s" % (e,)
1474
1475 output_sink(sh_location, sh)