blob: d13f82ac39b9d77f8fdf5f477d1eeff97570cc9d [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",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700425 info_dict["vboot_key"] + ".vbprivk",
426 info_dict["vboot_subkey"] + ".vbprivk",
427 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700428 img.name]
429 p = Run(cmd, stdout=subprocess.PIPE)
430 p.communicate()
431 assert p.returncode == 0, "vboot_signer of %s image failed" % path
432
Tao Baof3282b42015-04-01 11:21:55 -0700433 # Clean up the temp files.
434 img_unsigned.close()
435 img_keyblock.close()
436
Doug Zongkereef39442009-04-02 12:14:19 -0700437 img.seek(os.SEEK_SET, 0)
438 data = img.read()
439
440 ramdisk_img.close()
441 img.close()
442
443 return data
444
445
Doug Zongkerd5131602012-08-02 14:46:42 -0700446def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
447 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800448 """Return a File object (with name 'name') with the desired bootable
449 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700450 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
451 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800452 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700453
Doug Zongker55d93282011-01-25 17:03:34 -0800454 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
455 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700456 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800457 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700458
459 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
460 if os.path.exists(prebuilt_path):
461 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
462 return File.FromLocalFile(name, prebuilt_path)
463
464 print "building image from target_files %s..." % (tree_subdir,)
465 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
466 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
467 os.path.join(unpack_dir, fs_config),
468 info_dict)
469 if data:
470 return File(name, data)
471 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800472
Doug Zongkereef39442009-04-02 12:14:19 -0700473
Doug Zongker75f17362009-12-08 13:46:44 -0800474def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800475 """Unzip the given archive into a temporary directory and return the name.
476
477 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
478 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
479
480 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
481 main file), open for reading.
482 """
Doug Zongkereef39442009-04-02 12:14:19 -0700483
484 tmp = tempfile.mkdtemp(prefix="targetfiles-")
485 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800486
487 def unzip_to_dir(filename, dirname):
Dan Pasanen3d4039a2015-02-07 18:59:36 -0600488 subprocess.call(["rm", "-rf", dirname + filename, "targetfiles-*"])
Doug Zongker55d93282011-01-25 17:03:34 -0800489 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
490 if pattern is not None:
491 cmd.append(pattern)
492 p = Run(cmd, stdout=subprocess.PIPE)
493 p.communicate()
494 if p.returncode != 0:
495 raise ExternalError("failed to unzip input target-files \"%s\"" %
496 (filename,))
497
498 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
499 if m:
500 unzip_to_dir(m.group(1), tmp)
501 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
502 filename = m.group(1)
503 else:
504 unzip_to_dir(filename, tmp)
505
506 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700507
508
509def GetKeyPasswords(keylist):
510 """Given a list of keys, prompt the user to enter passwords for
511 those which require them. Return a {key: password} dict. password
512 will be None if the key has no password."""
513
Doug Zongker8ce7c252009-05-22 13:34:54 -0700514 no_passwords = []
515 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700516 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700517 devnull = open("/dev/null", "w+b")
518 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800519 # We don't need a password for things that aren't really keys.
520 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700521 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700522 continue
523
T.R. Fullhart37e10522013-03-18 10:31:26 -0700524 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700525 "-inform", "DER", "-nocrypt"],
526 stdin=devnull.fileno(),
527 stdout=devnull.fileno(),
528 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700529 p.communicate()
530 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700531 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700532 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700533 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700534 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
535 "-inform", "DER", "-passin", "pass:"],
536 stdin=devnull.fileno(),
537 stdout=devnull.fileno(),
538 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700539 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700540 if p.returncode == 0:
541 # Encrypted key with empty string as password.
542 key_passwords[k] = ''
543 elif stderr.startswith('Error decrypting key'):
544 # Definitely encrypted key.
545 # It would have said "Error reading key" if it didn't parse correctly.
546 need_passwords.append(k)
547 else:
548 # Potentially, a type of key that openssl doesn't understand.
549 # We'll let the routines in signapk.jar handle it.
550 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700551 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700552
T.R. Fullhart37e10522013-03-18 10:31:26 -0700553 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700554 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700555 return key_passwords
556
557
Doug Zongker951495f2009-08-14 12:44:19 -0700558def SignFile(input_name, output_name, key, password, align=None,
559 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700560 """Sign the input_name zip/jar/apk, producing output_name. Use the
561 given key and password (the latter may be None if the key does not
562 have a password.
563
564 If align is an integer > 1, zipalign is run to align stored files in
565 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700566
567 If whole_file is true, use the "-w" option to SignApk to embed a
568 signature that covers the whole file in the archive comment of the
569 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700570 """
Doug Zongker951495f2009-08-14 12:44:19 -0700571
Doug Zongkereef39442009-04-02 12:14:19 -0700572 if align == 0 or align == 1:
573 align = None
574
575 if align:
576 temp = tempfile.NamedTemporaryFile()
577 sign_name = temp.name
578 else:
579 sign_name = output_name
580
Baligh Uddin339ee492014-09-05 11:18:07 -0700581 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700582 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
583 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700584 if whole_file:
585 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700586 cmd.extend([key + OPTIONS.public_key_suffix,
587 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700588 input_name, sign_name])
589
590 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700591 if password is not None:
592 password += "\n"
593 p.communicate(password)
594 if p.returncode != 0:
595 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
596
597 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700598 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700599 p.communicate()
600 if p.returncode != 0:
601 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
602 temp.close()
603
604
Doug Zongker37974732010-09-16 17:44:38 -0700605def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700606 """Check the data string passed against the max size limit, if
607 any, for the given target. Raise exception if the data is too big.
608 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700609
Dan Albert8b72aef2015-03-23 19:13:21 -0700610 if target.endswith(".img"):
611 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700612 mount_point = "/" + target
613
Ying Wangf8824af2014-06-03 14:07:27 -0700614 fs_type = None
615 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700616 if info_dict["fstab"]:
Ethan Chenccc711c2014-06-02 16:49:59 -0700617 if mount_point == "/userdata_extra": mount_point = "/data"
618 if mount_point == "/userdata": mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700619 p = info_dict["fstab"][mount_point]
620 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800621 device = p.device
622 if "/" in device:
623 device = device[device.rfind("/")+1:]
624 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700625 if not fs_type or not limit:
626 return
Doug Zongkereef39442009-04-02 12:14:19 -0700627
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700628 if fs_type == "yaffs2":
629 # image size should be increased by 1/64th to account for the
630 # spare area (64 bytes per 2k page)
631 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800632 size = len(data)
633 pct = float(size) * 100.0 / limit
634 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
635 if pct >= 99.0:
636 raise ExternalError(msg)
637 elif pct >= 95.0:
638 print
639 print " WARNING: ", msg
640 print
641 elif OPTIONS.verbose:
642 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700643
644
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800645def ReadApkCerts(tf_zip):
646 """Given a target_files ZipFile, parse the META/apkcerts.txt file
647 and return a {package: cert} dict."""
648 certmap = {}
649 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
650 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700651 if not line:
652 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800653 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
654 r'private_key="(.*)"$', line)
655 if m:
656 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700657 public_key_suffix_len = len(OPTIONS.public_key_suffix)
658 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800659 if cert in SPECIAL_CERT_STRINGS and not privkey:
660 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700661 elif (cert.endswith(OPTIONS.public_key_suffix) and
662 privkey.endswith(OPTIONS.private_key_suffix) and
663 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
664 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800665 else:
666 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
667 return certmap
668
669
Doug Zongkereef39442009-04-02 12:14:19 -0700670COMMON_DOCSTRING = """
671 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700672 Prepend <dir>/bin to the list of places to search for binaries
673 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700674
Doug Zongker05d3dea2009-06-22 11:32:31 -0700675 -s (--device_specific) <file>
676 Path to the python module containing device-specific
677 releasetools code.
678
Doug Zongker8bec09e2009-11-30 15:37:14 -0800679 -x (--extra) <key=value>
680 Add a key/value pair to the 'extras' dict, which device-specific
681 extension code may look at.
682
Doug Zongkereef39442009-04-02 12:14:19 -0700683 -v (--verbose)
684 Show command lines being executed.
685
686 -h (--help)
687 Display this usage message and exit.
688"""
689
690def Usage(docstring):
691 print docstring.rstrip("\n")
692 print COMMON_DOCSTRING
693
694
695def ParseOptions(argv,
696 docstring,
697 extra_opts="", extra_long_opts=(),
698 extra_option_handler=None):
699 """Parse the options in argv and return any arguments that aren't
700 flags. docstring is the calling module's docstring, to be displayed
701 for errors and -h. extra_opts and extra_long_opts are for flags
702 defined by the caller, which are processed by passing them to
703 extra_option_handler."""
704
705 try:
706 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800707 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700708 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700709 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700710 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
711 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800712 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700713 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700714 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700715 Usage(docstring)
716 print "**", str(err), "**"
717 sys.exit(2)
718
Doug Zongkereef39442009-04-02 12:14:19 -0700719 for o, a in opts:
720 if o in ("-h", "--help"):
721 Usage(docstring)
722 sys.exit()
723 elif o in ("-v", "--verbose"):
724 OPTIONS.verbose = True
725 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700726 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700727 elif o in ("--signapk_path",):
728 OPTIONS.signapk_path = a
729 elif o in ("--extra_signapk_args",):
730 OPTIONS.extra_signapk_args = shlex.split(a)
731 elif o in ("--java_path",):
732 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700733 elif o in ("--java_args",):
734 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700735 elif o in ("--public_key_suffix",):
736 OPTIONS.public_key_suffix = a
737 elif o in ("--private_key_suffix",):
738 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800739 elif o in ("--boot_signer_path",):
740 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700741 elif o in ("--boot_signer_args",):
742 OPTIONS.boot_signer_args = shlex.split(a)
743 elif o in ("--verity_signer_path",):
744 OPTIONS.verity_signer_path = a
745 elif o in ("--verity_signer_args",):
746 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700747 elif o in ("-s", "--device_specific"):
748 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800749 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800750 key, value = a.split("=", 1)
751 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700752 else:
753 if extra_option_handler is None or not extra_option_handler(o, a):
754 assert False, "unknown option \"%s\"" % (o,)
755
Doug Zongker85448772014-09-09 14:59:20 -0700756 if OPTIONS.search_path:
757 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
758 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700759
760 return args
761
762
Doug Zongkerfc44a512014-08-26 13:10:25 -0700763def MakeTempFile(prefix=None, suffix=None):
764 """Make a temp file and add it to the list of things to be deleted
765 when Cleanup() is called. Return the filename."""
766 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
767 os.close(fd)
768 OPTIONS.tempfiles.append(fn)
769 return fn
770
771
Doug Zongkereef39442009-04-02 12:14:19 -0700772def Cleanup():
773 for i in OPTIONS.tempfiles:
774 if os.path.isdir(i):
775 shutil.rmtree(i)
776 else:
777 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700778
779
780class PasswordManager(object):
781 def __init__(self):
782 self.editor = os.getenv("EDITOR", None)
783 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
784
785 def GetPasswords(self, items):
786 """Get passwords corresponding to each string in 'items',
787 returning a dict. (The dict may have keys in addition to the
788 values in 'items'.)
789
790 Uses the passwords in $ANDROID_PW_FILE if available, letting the
791 user edit that file to add more needed passwords. If no editor is
792 available, or $ANDROID_PW_FILE isn't define, prompts the user
793 interactively in the ordinary way.
794 """
795
796 current = self.ReadFile()
797
798 first = True
799 while True:
800 missing = []
801 for i in items:
802 if i not in current or not current[i]:
803 missing.append(i)
804 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700805 if not missing:
806 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700807
808 for i in missing:
809 current[i] = ""
810
811 if not first:
812 print "key file %s still missing some passwords." % (self.pwfile,)
813 answer = raw_input("try to edit again? [y]> ").strip()
814 if answer and answer[0] not in 'yY':
815 raise RuntimeError("key passwords unavailable")
816 first = False
817
818 current = self.UpdateAndReadFile(current)
819
Dan Albert8b72aef2015-03-23 19:13:21 -0700820 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700821 """Prompt the user to enter a value (password) for each key in
822 'current' whose value is fales. Returns a new dict with all the
823 values.
824 """
825 result = {}
826 for k, v in sorted(current.iteritems()):
827 if v:
828 result[k] = v
829 else:
830 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700831 result[k] = getpass.getpass(
832 "Enter password for %s key> " % k).strip()
833 if result[k]:
834 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700835 return result
836
837 def UpdateAndReadFile(self, current):
838 if not self.editor or not self.pwfile:
839 return self.PromptResult(current)
840
841 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700843 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
844 f.write("# (Additional spaces are harmless.)\n\n")
845
846 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700847 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
848 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700849 f.write("[[[ %s ]]] %s\n" % (v, k))
850 if not v and first_line is None:
851 # position cursor on first line with no password.
852 first_line = i + 4
853 f.close()
854
855 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
856 _, _ = p.communicate()
857
858 return self.ReadFile()
859
860 def ReadFile(self):
861 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700862 if self.pwfile is None:
863 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700864 try:
865 f = open(self.pwfile, "r")
866 for line in f:
867 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700868 if not line or line[0] == '#':
869 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700870 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
871 if not m:
872 print "failed to parse password file: ", line
873 else:
874 result[m.group(2)] = m.group(1)
875 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700876 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700877 if e.errno != errno.ENOENT:
878 print "error reading password file: ", str(e)
879 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700880
881
Dan Albert8e0178d2015-01-27 15:53:15 -0800882def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
883 compress_type=None):
884 import datetime
885
886 # http://b/18015246
887 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
888 # for files larger than 2GiB. We can work around this by adjusting their
889 # limit. Note that `zipfile.writestr()` will not work for strings larger than
890 # 2GiB. The Python interpreter sometimes rejects strings that large (though
891 # it isn't clear to me exactly what circumstances cause this).
892 # `zipfile.write()` must be used directly to work around this.
893 #
894 # This mess can be avoided if we port to python3.
895 saved_zip64_limit = zipfile.ZIP64_LIMIT
896 zipfile.ZIP64_LIMIT = (1 << 32) - 1
897
898 if compress_type is None:
899 compress_type = zip_file.compression
900 if arcname is None:
901 arcname = filename
902
903 saved_stat = os.stat(filename)
904
905 try:
906 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
907 # file to be zipped and reset it when we're done.
908 os.chmod(filename, perms)
909
910 # Use a fixed timestamp so the output is repeatable.
911 epoch = datetime.datetime.fromtimestamp(0)
912 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
913 os.utime(filename, (timestamp, timestamp))
914
915 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
916 finally:
917 os.chmod(filename, saved_stat.st_mode)
918 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
919 zipfile.ZIP64_LIMIT = saved_zip64_limit
920
921
Tao Bao58c1b962015-05-20 09:32:18 -0700922def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700923 compress_type=None):
924 """Wrap zipfile.writestr() function to work around the zip64 limit.
925
926 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
927 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
928 when calling crc32(bytes).
929
930 But it still works fine to write a shorter string into a large zip file.
931 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
932 when we know the string won't be too long.
933 """
934
935 saved_zip64_limit = zipfile.ZIP64_LIMIT
936 zipfile.ZIP64_LIMIT = (1 << 32) - 1
937
938 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
939 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700940 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700941 if perms is None:
942 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800943 else:
Tao Baof3282b42015-04-01 11:21:55 -0700944 zinfo = zinfo_or_arcname
945
946 # If compress_type is given, it overrides the value in zinfo.
947 if compress_type is not None:
948 zinfo.compress_type = compress_type
949
Tao Bao58c1b962015-05-20 09:32:18 -0700950 # If perms is given, it has a priority.
951 if perms is not None:
952 zinfo.external_attr = perms << 16
953
Tao Baof3282b42015-04-01 11:21:55 -0700954 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700955 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
956
Dan Albert8b72aef2015-03-23 19:13:21 -0700957 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700958 zipfile.ZIP64_LIMIT = saved_zip64_limit
959
960
961def ZipClose(zip_file):
962 # http://b/18015246
963 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
964 # central directory.
965 saved_zip64_limit = zipfile.ZIP64_LIMIT
966 zipfile.ZIP64_LIMIT = (1 << 32) - 1
967
968 zip_file.close()
969
970 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700971
972
973class DeviceSpecificParams(object):
974 module = None
975 def __init__(self, **kwargs):
976 """Keyword arguments to the constructor become attributes of this
977 object, which is passed to all functions in the device-specific
978 module."""
979 for k, v in kwargs.iteritems():
980 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800981 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700982
983 if self.module is None:
984 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700985 if not path:
986 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700987 try:
988 if os.path.isdir(path):
989 info = imp.find_module("releasetools", [path])
990 else:
991 d, f = os.path.split(path)
992 b, x = os.path.splitext(f)
993 if x == ".py":
994 f = b
995 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -0800996 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700997 self.module = imp.load_module("device_specific", *info)
998 except ImportError:
999 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001000
1001 def _DoCall(self, function_name, *args, **kwargs):
1002 """Call the named function in the device-specific module, passing
1003 the given args and kwargs. The first argument to the call will be
1004 the DeviceSpecific object itself. If there is no module, or the
1005 module does not define the function, return the value of the
1006 'default' kwarg (which itself defaults to None)."""
1007 if self.module is None or not hasattr(self.module, function_name):
1008 return kwargs.get("default", None)
1009 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1010
1011 def FullOTA_Assertions(self):
1012 """Called after emitting the block of assertions at the top of a
1013 full OTA package. Implementations can add whatever additional
1014 assertions they like."""
1015 return self._DoCall("FullOTA_Assertions")
1016
Doug Zongkere5ff5902012-01-17 10:55:37 -08001017 def FullOTA_InstallBegin(self):
1018 """Called at the start of full OTA installation."""
1019 return self._DoCall("FullOTA_InstallBegin")
1020
Doug Zongker05d3dea2009-06-22 11:32:31 -07001021 def FullOTA_InstallEnd(self):
1022 """Called at the end of full OTA installation; typically this is
1023 used to install the image for the device's baseband processor."""
1024 return self._DoCall("FullOTA_InstallEnd")
1025
M1chad27ccf72014-11-25 15:30:48 +01001026 def FullOTA_PostValidate(self):
1027 """Called after installing and validating /system; typically this is
1028 used to resize the system partition after a block based installation."""
1029 return self._DoCall("FullOTA_PostValidate")
1030
Doug Zongker05d3dea2009-06-22 11:32:31 -07001031 def IncrementalOTA_Assertions(self):
1032 """Called after emitting the block of assertions at the top of an
1033 incremental OTA package. Implementations can add whatever
1034 additional assertions they like."""
1035 return self._DoCall("IncrementalOTA_Assertions")
1036
Doug Zongkere5ff5902012-01-17 10:55:37 -08001037 def IncrementalOTA_VerifyBegin(self):
1038 """Called at the start of the verification phase of incremental
1039 OTA installation; additional checks can be placed here to abort
1040 the script before any changes are made."""
1041 return self._DoCall("IncrementalOTA_VerifyBegin")
1042
Doug Zongker05d3dea2009-06-22 11:32:31 -07001043 def IncrementalOTA_VerifyEnd(self):
1044 """Called at the end of the verification phase of incremental OTA
1045 installation; additional checks can be placed here to abort the
1046 script before any changes are made."""
1047 return self._DoCall("IncrementalOTA_VerifyEnd")
1048
Doug Zongkere5ff5902012-01-17 10:55:37 -08001049 def IncrementalOTA_InstallBegin(self):
1050 """Called at the start of incremental OTA installation (after
1051 verification is complete)."""
1052 return self._DoCall("IncrementalOTA_InstallBegin")
1053
Doug Zongker05d3dea2009-06-22 11:32:31 -07001054 def IncrementalOTA_InstallEnd(self):
1055 """Called at the end of incremental OTA installation; typically
1056 this is used to install the image for the device's baseband
1057 processor."""
1058 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001059
1060class File(object):
1061 def __init__(self, name, data):
1062 self.name = name
1063 self.data = data
1064 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001065 self.sha1 = sha1(data).hexdigest()
1066
1067 @classmethod
1068 def FromLocalFile(cls, name, diskname):
1069 f = open(diskname, "rb")
1070 data = f.read()
1071 f.close()
1072 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001073
1074 def WriteToTemp(self):
1075 t = tempfile.NamedTemporaryFile()
1076 t.write(self.data)
1077 t.flush()
1078 return t
1079
Geremy Condra36bd3652014-02-06 19:45:10 -08001080 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001081 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001082
1083DIFF_PROGRAM_BY_EXT = {
1084 ".gz" : "imgdiff",
1085 ".zip" : ["imgdiff", "-z"],
1086 ".jar" : ["imgdiff", "-z"],
1087 ".apk" : ["imgdiff", "-z"],
1088 ".img" : "imgdiff",
1089 }
1090
1091class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001092 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001093 self.tf = tf
1094 self.sf = sf
1095 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001096 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001097
1098 def ComputePatch(self):
1099 """Compute the patch (as a string of data) needed to turn sf into
1100 tf. Returns the same tuple as GetPatch()."""
1101
1102 tf = self.tf
1103 sf = self.sf
1104
Doug Zongker24cd2802012-08-14 16:36:15 -07001105 if self.diff_program:
1106 diff_program = self.diff_program
1107 else:
1108 ext = os.path.splitext(tf.name)[1]
1109 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001110
1111 ttemp = tf.WriteToTemp()
1112 stemp = sf.WriteToTemp()
1113
1114 ext = os.path.splitext(tf.name)[1]
1115
1116 try:
1117 ptemp = tempfile.NamedTemporaryFile()
1118 if isinstance(diff_program, list):
1119 cmd = copy.copy(diff_program)
1120 else:
1121 cmd = [diff_program]
1122 cmd.append(stemp.name)
1123 cmd.append(ttemp.name)
1124 cmd.append(ptemp.name)
1125 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001126 err = []
1127 def run():
1128 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001129 if e:
1130 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001131 th = threading.Thread(target=run)
1132 th.start()
1133 th.join(timeout=300) # 5 mins
1134 if th.is_alive():
1135 print "WARNING: diff command timed out"
1136 p.terminate()
1137 th.join(5)
1138 if th.is_alive():
1139 p.kill()
1140 th.join()
1141
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001142 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001143 print "WARNING: failure running %s:\n%s\n" % (
1144 diff_program, "".join(err))
1145 self.patch = None
1146 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001147 diff = ptemp.read()
1148 finally:
1149 ptemp.close()
1150 stemp.close()
1151 ttemp.close()
1152
1153 self.patch = diff
1154 return self.tf, self.sf, self.patch
1155
1156
1157 def GetPatch(self):
1158 """Return a tuple (target_file, source_file, patch_data).
1159 patch_data may be None if ComputePatch hasn't been called, or if
1160 computing the patch failed."""
1161 return self.tf, self.sf, self.patch
1162
1163
1164def ComputeDifferences(diffs):
1165 """Call ComputePatch on all the Difference objects in 'diffs'."""
1166 print len(diffs), "diffs to compute"
1167
1168 # Do the largest files first, to try and reduce the long-pole effect.
1169 by_size = [(i.tf.size, i) for i in diffs]
1170 by_size.sort(reverse=True)
1171 by_size = [i[1] for i in by_size]
1172
1173 lock = threading.Lock()
1174 diff_iter = iter(by_size) # accessed under lock
1175
1176 def worker():
1177 try:
1178 lock.acquire()
1179 for d in diff_iter:
1180 lock.release()
1181 start = time.time()
1182 d.ComputePatch()
1183 dur = time.time() - start
1184 lock.acquire()
1185
1186 tf, sf, patch = d.GetPatch()
1187 if sf.name == tf.name:
1188 name = tf.name
1189 else:
1190 name = "%s (%s)" % (tf.name, sf.name)
1191 if patch is None:
1192 print "patching failed! %s" % (name,)
1193 else:
1194 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1195 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1196 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001197 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001198 print e
1199 raise
1200
1201 # start worker threads; wait for them all to finish.
1202 threads = [threading.Thread(target=worker)
1203 for i in range(OPTIONS.worker_threads)]
1204 for th in threads:
1205 th.start()
1206 while threads:
1207 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001208
1209
Dan Albert8b72aef2015-03-23 19:13:21 -07001210class BlockDifference(object):
1211 def __init__(self, partition, tgt, src=None, check_first_block=False,
1212 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001213 self.tgt = tgt
1214 self.src = src
1215 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001216 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001217
Tao Bao5ece99d2015-05-12 11:42:31 -07001218 # Due to http://b/20939131, check_first_block is disabled temporarily.
1219 assert not self.check_first_block
1220
Tao Baodd2a5892015-03-12 12:32:37 -07001221 if version is None:
1222 version = 1
1223 if OPTIONS.info_dict:
1224 version = max(
1225 int(i) for i in
1226 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1227 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001228
1229 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001230 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001231 tmpdir = tempfile.mkdtemp()
1232 OPTIONS.tempfiles.append(tmpdir)
1233 self.path = os.path.join(tmpdir, partition)
1234 b.Compute(self.path)
1235
1236 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1237
1238 def WriteScript(self, script, output_zip, progress=None):
1239 if not self.src:
1240 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001241 script.Print("Patching %s image unconditionally..." % (self.partition,))
1242 else:
1243 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001244
Dan Albert8b72aef2015-03-23 19:13:21 -07001245 if progress:
1246 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001247 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001248 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001249
1250 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001251 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001252 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001253 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001255 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1256 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001257 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001258 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1259 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001260 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001261 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001262 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001263 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001264 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001265 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001266 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001267 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001269
Tao Baodd2a5892015-03-12 12:32:37 -07001270 # When generating incrementals for the system and vendor partitions,
1271 # explicitly check the first block (which contains the superblock) of
1272 # the partition to see if it's what we expect. If this check fails,
1273 # give an explicit log message about the partition having been
1274 # remounted R/W (the most likely explanation) and the need to flash to
1275 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001276 if self.check_first_block:
1277 self._CheckFirstBlock(script)
1278
Tao Baodd2a5892015-03-12 12:32:37 -07001279 # Abort the OTA update. Note that the incremental OTA cannot be applied
1280 # even if it may match the checksum of the target partition.
1281 # a) If version < 3, operations like move and erase will make changes
1282 # unconditionally and damage the partition.
1283 # b) If version >= 3, it won't even reach here.
1284 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1285 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001286
Tao Bao5fcaaef2015-06-01 13:40:49 -07001287 def _WritePostInstallVerifyScript(self, script):
1288 partition = self.partition
1289 script.Print('Verifying the updated %s image...' % (partition,))
1290 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1291 ranges = self.tgt.care_map
1292 ranges_str = ranges.to_string_raw()
1293 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1294 self.device, ranges_str,
1295 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001296
1297 # Bug: 20881595
1298 # Verify that extended blocks are really zeroed out.
1299 if self.tgt.extended:
1300 ranges_str = self.tgt.extended.to_string_raw()
1301 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1302 self.device, ranges_str,
1303 self._HashZeroBlocks(self.tgt.extended.size())))
1304 script.Print('Verified the updated %s image.' % (partition,))
1305 script.AppendExtra(
1306 'else\n'
1307 ' abort("%s partition has unexpected non-zero contents after OTA '
1308 'update");\n'
1309 'endif;' % (partition,))
1310 else:
1311 script.Print('Verified the updated %s image.' % (partition,))
1312
Tao Bao5fcaaef2015-06-01 13:40:49 -07001313 script.AppendExtra(
1314 'else\n'
1315 ' abort("%s partition has unexpected contents after OTA update");\n'
1316 'endif;' % (partition,))
1317
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001318 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001319 ZipWrite(output_zip,
1320 '{}.transfer.list'.format(self.path),
1321 '{}.transfer.list'.format(self.partition))
1322 ZipWrite(output_zip,
1323 '{}.new.dat'.format(self.path),
1324 '{}.new.dat'.format(self.partition))
1325 ZipWrite(output_zip,
1326 '{}.patch.dat'.format(self.path),
1327 '{}.patch.dat'.format(self.partition),
1328 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001329
Dan Albert8e0178d2015-01-27 15:53:15 -08001330 call = ('block_image_update("{device}", '
1331 'package_extract_file("{partition}.transfer.list"), '
1332 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1333 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001335
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001337 data = source.ReadRangeSet(ranges)
1338 ctx = sha1()
1339
1340 for p in data:
1341 ctx.update(p)
1342
1343 return ctx.hexdigest()
1344
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001345 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1346 """Return the hash value for all zero blocks."""
1347 zero_block = '\x00' * 4096
1348 ctx = sha1()
1349 for _ in range(num_blocks):
1350 ctx.update(zero_block)
1351
1352 return ctx.hexdigest()
1353
Tao Bao5ece99d2015-05-12 11:42:31 -07001354 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1355 # remounting R/W. Will change the checking to a finer-grained way to
1356 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001357 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001358 r = rangelib.RangeSet((0, 1))
1359 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001360
1361 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1362 'abort("%s has been remounted R/W; '
1363 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001364 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001365 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001366
1367DataImage = blockimgdiff.DataImage
1368
1369
Doug Zongker96a57e72010-09-26 14:57:41 -07001370# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001371PARTITION_TYPES = {
1372 "yaffs2": "MTD",
1373 "mtd": "MTD",
1374 "ext4": "EMMC",
1375 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001376 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001377 "squashfs": "EMMC",
1378 "ext2": "EMMC",
1379 "ext3": "EMMC",
Stephen Bird9f657582015-10-08 02:05:06 -07001380 "vfat": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001381}
Doug Zongker96a57e72010-09-26 14:57:41 -07001382
1383def GetTypeAndDevice(mount_point, info):
1384 fstab = info["fstab"]
1385 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001386 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1387 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001388 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001389 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001390
1391
1392def ParseCertificate(data):
1393 """Parse a PEM-format certificate."""
1394 cert = []
1395 save = False
1396 for line in data.split("\n"):
1397 if "--END CERTIFICATE--" in line:
1398 break
1399 if save:
1400 cert.append(line)
1401 if "--BEGIN CERTIFICATE--" in line:
1402 save = True
1403 cert = "".join(cert).decode('base64')
1404 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001405
Doug Zongker412c02f2014-02-13 10:58:24 -08001406def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1407 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001408 """Generate a binary patch that creates the recovery image starting
1409 with the boot image. (Most of the space in these images is just the
1410 kernel, which is identical for the two, so the resulting patch
1411 should be efficient.) Add it to the output zip, along with a shell
1412 script that is run from init.rc on first boot to actually do the
1413 patching and install the new recovery image.
1414
1415 recovery_img and boot_img should be File objects for the
1416 corresponding images. info should be the dictionary returned by
1417 common.LoadInfoDict() on the input target_files.
1418 """
1419
Doug Zongker412c02f2014-02-13 10:58:24 -08001420 if info_dict is None:
1421 info_dict = OPTIONS.info_dict
1422
Doug Zongkerc9253822014-02-04 12:17:58 -08001423 diff_program = ["imgdiff"]
1424 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1425 if os.path.exists(path):
1426 diff_program.append("-b")
1427 diff_program.append(path)
1428 bonus_args = "-b /system/etc/recovery-resource.dat"
1429 else:
1430 bonus_args = ""
1431
1432 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1433 _, _, patch = d.ComputePatch()
1434 output_sink("recovery-from-boot.p", patch)
1435
Dan Albertebb19aa2015-03-27 19:11:53 -07001436 try:
1437 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1438 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1439 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001440 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001441
1442 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001443if [ -f /system/etc/recovery-transform.sh ]; then
1444 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1445fi
1446
Doug Zongkerc9253822014-02-04 12:17:58 -08001447if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1448 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"
1449else
1450 log -t recovery "Recovery image already installed"
1451fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001452""" % {'boot_size': boot_img.size,
1453 'boot_sha1': boot_img.sha1,
1454 'recovery_size': recovery_img.size,
1455 'recovery_sha1': recovery_img.sha1,
1456 'boot_type': boot_type,
1457 'boot_device': boot_device,
1458 'recovery_type': recovery_type,
1459 'recovery_device': recovery_device,
1460 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001461
1462 # The install script location moved from /system/etc to /system/bin
1463 # in the L release. Parse the init.rc file to find out where the
1464 # target-files expects it to be, and put it there.
1465 sh_location = "etc/install-recovery.sh"
1466 try:
1467 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1468 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001469 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001470 if m:
1471 sh_location = m.group(1)
1472 print "putting script in", sh_location
1473 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001474 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001475 print "failed to read init.rc: %s" % (e,)
1476
1477 output_sink(sh_location, sh)