blob: 3ea0acb331de0c2ab9188931f27f0b853f79a174 [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
Tao Baoe9647142015-08-07 19:49:45 -070071# Stash size cannot exceed cache_size * threshold.
72OPTIONS.cache_size = None
73OPTIONS.stash_threshold = 0.8
74
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080075
Dan Albert8b72aef2015-03-23 19:13:21 -070076class ExternalError(RuntimeError):
77 pass
Doug Zongkereef39442009-04-02 12:14:19 -070078
79
80def Run(args, **kwargs):
81 """Create and return a subprocess.Popen object, printing the command
82 line on the terminal if -v was specified."""
83 if OPTIONS.verbose:
84 print " running: ", " ".join(args)
85 return subprocess.Popen(args, **kwargs)
86
87
Ying Wang7e6d4e42010-12-13 16:25:36 -080088def CloseInheritedPipes():
89 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
90 before doing other work."""
91 if platform.system() != "Darwin":
92 return
93 for d in range(3, 1025):
94 try:
95 stat = os.fstat(d)
96 if stat is not None:
97 pipebit = stat[0] & 0x1000
98 if pipebit != 0:
99 os.close(d)
100 except OSError:
101 pass
102
103
Dan Albert8b72aef2015-03-23 19:13:21 -0700104def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700105 """Read and parse the META/misc_info.txt key/value pairs from the
106 input target files and return a dict."""
107
Doug Zongkerc9253822014-02-04 12:17:58 -0800108 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700109 if isinstance(input_file, zipfile.ZipFile):
110 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800111 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700112 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 try:
114 with open(path) as f:
115 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700116 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800117 if e.errno == errno.ENOENT:
118 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700119 d = {}
120 try:
Michael Runge6e836112014-04-15 17:40:21 -0700121 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700122 except KeyError:
123 # ok if misc_info.txt doesn't exist
124 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700125
Doug Zongker37974732010-09-16 17:44:38 -0700126 # backwards compatibility: These values used to be in their own
127 # files. Look for them, in case we're processing an old
128 # target_files zip.
129
130 if "mkyaffs2_extra_flags" not in d:
131 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700132 d["mkyaffs2_extra_flags"] = read_helper(
133 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700134 except KeyError:
135 # ok if flags don't exist
136 pass
137
138 if "recovery_api_version" not in d:
139 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700140 d["recovery_api_version"] = read_helper(
141 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700142 except KeyError:
143 raise ValueError("can't find recovery API version in input target-files")
144
145 if "tool_extensions" not in d:
146 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800147 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700148 except KeyError:
149 # ok if extensions don't exist
150 pass
151
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800152 if "fstab_version" not in d:
153 d["fstab_version"] = "1"
154
Ameya Thakure5b5a272013-07-29 17:39:37 -0700155 if "device_type" not in d:
156 d["device_type"] = "MMC"
Doug Zongker37974732010-09-16 17:44:38 -0700157 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800158 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700159 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700160 if not line:
161 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700162 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700163 if not value:
164 continue
Doug Zongker37974732010-09-16 17:44:38 -0700165 if name == "blocksize":
166 d[name] = value
167 else:
168 d[name + "_size"] = value
169 except KeyError:
170 pass
171
172 def makeint(key):
173 if key in d:
174 d[key] = int(d[key], 0)
175
176 makeint("recovery_api_version")
177 makeint("blocksize")
178 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700179 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700180 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700181 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700182 makeint("recovery_size")
183 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800184 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700185
Ameya Thakure5b5a272013-07-29 17:39:37 -0700186 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d["device_type"])
Doug Zongkerc9253822014-02-04 12:17:58 -0800187 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700188 return d
189
Doug Zongkerc9253822014-02-04 12:17:58 -0800190def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700191 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800192 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 except KeyError:
194 print "Warning: could not find SYSTEM/build.prop in %s" % zip
195 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700196 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700197
Michael Runge6e836112014-04-15 17:40:21 -0700198def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700199 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700200 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700201 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700202 if not line or line.startswith("#"):
203 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700204 if "=" in line:
205 name, value = line.split("=", 1)
206 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700207 return d
208
Ameya Thakure5b5a272013-07-29 17:39:37 -0700209def LoadRecoveryFSTab(read_helper, fstab_version, type):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700210 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700211 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700212 self.mount_point = mount_point
213 self.fs_type = fs_type
214 self.device = device
215 self.length = length
216 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700217 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700218
219 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800220 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700221 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800222 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700223 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700224
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800225 if fstab_version == 1:
226 d = {}
227 for line in data.split("\n"):
228 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700229 if not line or line.startswith("#"):
230 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800231 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700232 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800234 options = None
235 if len(pieces) >= 4:
236 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700237 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800238 if len(pieces) >= 5:
239 options = pieces[4]
240 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700241 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800242 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800243 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700245
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 mount_point = pieces[0]
247 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248 if options:
249 options = options.split(",")
250 for i in options:
251 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
257 device=pieces[2], length=length,
258 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800259
260 elif fstab_version == 2:
261 d = {}
262 for line in data.split("\n"):
263 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700264 if not line or line.startswith("#"):
265 continue
Tao Bao548eb762015-06-10 12:32:41 -0700266 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800267 pieces = line.split()
268 if len(pieces) != 5:
269 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
270
271 # Ignore entries that are managed by vold
272 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700273 if "voldmanaged=" in options:
274 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800275
276 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700277 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800278 options = options.split(",")
279 for i in options:
280 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700281 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800282 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800283 # Ignore all unknown options in the unified fstab
284 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800285
Tao Bao548eb762015-06-10 12:32:41 -0700286 mount_flags = pieces[3]
287 # Honor the SELinux context if present.
288 context = None
289 for i in mount_flags.split(","):
290 if i.startswith("context="):
291 context = i
292
Dan Albert8b72aef2015-03-23 19:13:21 -0700293 mount_point = pieces[1]
294 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700295 device=pieces[0], length=length,
296 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800297
298 else:
299 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
300
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700301 return d
302
303
Doug Zongker37974732010-09-16 17:44:38 -0700304def DumpInfoDict(d):
305 for k, v in sorted(d.items()):
306 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700307
Dan Albert8b72aef2015-03-23 19:13:21 -0700308
Doug Zongkerd5131602012-08-02 14:46:42 -0700309def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700310 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700311 'sourcedir'), and turn them into a boot image. Return the image
312 data, or None if sourcedir does not appear to contains files for
313 building the requested image."""
314
315 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
316 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
317 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700318
Doug Zongkerd5131602012-08-02 14:46:42 -0700319 if info_dict is None:
320 info_dict = OPTIONS.info_dict
321
Doug Zongkereef39442009-04-02 12:14:19 -0700322 ramdisk_img = tempfile.NamedTemporaryFile()
323 img = tempfile.NamedTemporaryFile()
324
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700325 if os.access(fs_config_file, os.F_OK):
326 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
327 else:
328 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
329 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700330 p2 = Run(["minigzip"],
331 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700332
333 p2.wait()
334 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700335 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
336 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700337
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000338 """check if uboot is requested"""
339 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200340 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000341 cmd = ["mkimage"]
342 for argument in open(fn).read().rstrip("\n").split(" "):
343 cmd.append(argument)
344 cmd.append("-d")
345 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
346 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200347
Tao Baod95e9fd2015-03-29 23:07:41 -0700348 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000349 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
350 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
351 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700352
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000353 fn = os.path.join(sourcedir, "second")
354 if os.access(fn, os.F_OK):
355 cmd.append("--second")
356 cmd.append(fn)
357
358 fn = os.path.join(sourcedir, "cmdline")
359 if os.access(fn, os.F_OK):
360 cmd.append("--cmdline")
361 cmd.append(open(fn).read().rstrip("\n"))
362
363 fn = os.path.join(sourcedir, "base")
364 if os.access(fn, os.F_OK):
365 cmd.append("--base")
366 cmd.append(open(fn).read().rstrip("\n"))
367
368 fn = os.path.join(sourcedir, "tagsaddr")
369 if os.access(fn, os.F_OK):
370 cmd.append("--tags-addr")
371 cmd.append(open(fn).read().rstrip("\n"))
372
Ameya Thakure5b5a272013-07-29 17:39:37 -0700373 fn = os.path.join(sourcedir, "tags_offset")
374 if os.access(fn, os.F_OK):
375 cmd.append("--tags_offset")
376 cmd.append(open(fn).read().rstrip("\n"))
377
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000378 fn = os.path.join(sourcedir, "ramdisk_offset")
379 if os.access(fn, os.F_OK):
380 cmd.append("--ramdisk_offset")
381 cmd.append(open(fn).read().rstrip("\n"))
382
383 fn = os.path.join(sourcedir, "dt_args")
384 if os.access(fn, os.F_OK):
385 cmd.append("--dt")
386 cmd.append(open(fn).read().rstrip("\n"))
387
388 fn = os.path.join(sourcedir, "pagesize")
389 if os.access(fn, os.F_OK):
390 cmd.append("--pagesize")
391 cmd.append(open(fn).read().rstrip("\n"))
392
393 args = info_dict.get("mkbootimg_args", None)
394 if args and args.strip():
395 cmd.extend(shlex.split(args))
396
397 img_unsigned = None
398 if info_dict.get("vboot", None):
399 img_unsigned = tempfile.NamedTemporaryFile()
400 cmd.extend(["--ramdisk", ramdisk_img.name,
401 "--output", img_unsigned.name])
402 else:
403 cmd.extend(["--ramdisk", ramdisk_img.name,
404 "--output", img.name])
405
Doug Zongker38a649f2009-06-17 09:07:09 -0700406 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700407 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700408 assert p.returncode == 0, "mkbootimg of %s image failed" % (
409 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700410
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100411 if (info_dict.get("boot_signer", None) == "true" and
412 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700413 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700414 cmd = [OPTIONS.boot_signer_path]
415 cmd.extend(OPTIONS.boot_signer_args)
416 cmd.extend([path, img.name,
417 info_dict["verity_key"] + ".pk8",
418 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700419 p = Run(cmd, stdout=subprocess.PIPE)
420 p.communicate()
421 assert p.returncode == 0, "boot_signer of %s image failed" % path
422
Tao Baod95e9fd2015-03-29 23:07:41 -0700423 # Sign the image if vboot is non-empty.
424 elif info_dict.get("vboot", None):
425 path = "/" + os.path.basename(sourcedir).lower()
426 img_keyblock = tempfile.NamedTemporaryFile()
427 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
428 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700429 info_dict["vboot_key"] + ".vbprivk",
430 info_dict["vboot_subkey"] + ".vbprivk",
431 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700432 img.name]
433 p = Run(cmd, stdout=subprocess.PIPE)
434 p.communicate()
435 assert p.returncode == 0, "vboot_signer of %s image failed" % path
436
Tao Baof3282b42015-04-01 11:21:55 -0700437 # Clean up the temp files.
438 img_unsigned.close()
439 img_keyblock.close()
440
Doug Zongkereef39442009-04-02 12:14:19 -0700441 img.seek(os.SEEK_SET, 0)
442 data = img.read()
443
444 ramdisk_img.close()
445 img.close()
446
447 return data
448
449
Doug Zongkerd5131602012-08-02 14:46:42 -0700450def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
451 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800452 """Return a File object (with name 'name') with the desired bootable
453 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700454 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
455 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800456 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700457
Doug Zongker55d93282011-01-25 17:03:34 -0800458 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
459 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700460 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800461 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700462
463 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
464 if os.path.exists(prebuilt_path):
465 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
466 return File.FromLocalFile(name, prebuilt_path)
467
468 print "building image from target_files %s..." % (tree_subdir,)
469 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
470 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
471 os.path.join(unpack_dir, fs_config),
472 info_dict)
473 if data:
474 return File(name, data)
475 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800476
Doug Zongkereef39442009-04-02 12:14:19 -0700477
Doug Zongker75f17362009-12-08 13:46:44 -0800478def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800479 """Unzip the given archive into a temporary directory and return the name.
480
481 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
482 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
483
484 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
485 main file), open for reading.
486 """
Doug Zongkereef39442009-04-02 12:14:19 -0700487
488 tmp = tempfile.mkdtemp(prefix="targetfiles-")
489 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800490
491 def unzip_to_dir(filename, dirname):
Dan Pasanen3d4039a2015-02-07 18:59:36 -0600492 subprocess.call(["rm", "-rf", dirname + filename, "targetfiles-*"])
Doug Zongker55d93282011-01-25 17:03:34 -0800493 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
494 if pattern is not None:
495 cmd.append(pattern)
496 p = Run(cmd, stdout=subprocess.PIPE)
497 p.communicate()
498 if p.returncode != 0:
499 raise ExternalError("failed to unzip input target-files \"%s\"" %
500 (filename,))
501
502 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
503 if m:
504 unzip_to_dir(m.group(1), tmp)
505 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
506 filename = m.group(1)
507 else:
508 unzip_to_dir(filename, tmp)
509
510 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700511
512
513def GetKeyPasswords(keylist):
514 """Given a list of keys, prompt the user to enter passwords for
515 those which require them. Return a {key: password} dict. password
516 will be None if the key has no password."""
517
Doug Zongker8ce7c252009-05-22 13:34:54 -0700518 no_passwords = []
519 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700520 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700521 devnull = open("/dev/null", "w+b")
522 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800523 # We don't need a password for things that aren't really keys.
524 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700525 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700526 continue
527
T.R. Fullhart37e10522013-03-18 10:31:26 -0700528 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700529 "-inform", "DER", "-nocrypt"],
530 stdin=devnull.fileno(),
531 stdout=devnull.fileno(),
532 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700533 p.communicate()
534 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700536 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700537 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700538 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
539 "-inform", "DER", "-passin", "pass:"],
540 stdin=devnull.fileno(),
541 stdout=devnull.fileno(),
542 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700543 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700544 if p.returncode == 0:
545 # Encrypted key with empty string as password.
546 key_passwords[k] = ''
547 elif stderr.startswith('Error decrypting key'):
548 # Definitely encrypted key.
549 # It would have said "Error reading key" if it didn't parse correctly.
550 need_passwords.append(k)
551 else:
552 # Potentially, a type of key that openssl doesn't understand.
553 # We'll let the routines in signapk.jar handle it.
554 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700555 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700556
T.R. Fullhart37e10522013-03-18 10:31:26 -0700557 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700558 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700559 return key_passwords
560
561
Doug Zongker951495f2009-08-14 12:44:19 -0700562def SignFile(input_name, output_name, key, password, align=None,
563 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700564 """Sign the input_name zip/jar/apk, producing output_name. Use the
565 given key and password (the latter may be None if the key does not
566 have a password.
567
568 If align is an integer > 1, zipalign is run to align stored files in
569 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700570
571 If whole_file is true, use the "-w" option to SignApk to embed a
572 signature that covers the whole file in the archive comment of the
573 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700574 """
Doug Zongker951495f2009-08-14 12:44:19 -0700575
Doug Zongkereef39442009-04-02 12:14:19 -0700576 if align == 0 or align == 1:
577 align = None
578
579 if align:
580 temp = tempfile.NamedTemporaryFile()
581 sign_name = temp.name
582 else:
583 sign_name = output_name
584
Baligh Uddin339ee492014-09-05 11:18:07 -0700585 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700586 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
587 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700588 if whole_file:
589 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700590 cmd.extend([key + OPTIONS.public_key_suffix,
591 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700592 input_name, sign_name])
593
594 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700595 if password is not None:
596 password += "\n"
597 p.communicate(password)
598 if p.returncode != 0:
599 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
600
601 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700602 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700603 p.communicate()
604 if p.returncode != 0:
605 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
606 temp.close()
607
608
Doug Zongker37974732010-09-16 17:44:38 -0700609def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700610 """Check the data string passed against the max size limit, if
611 any, for the given target. Raise exception if the data is too big.
612 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700613
Dan Albert8b72aef2015-03-23 19:13:21 -0700614 if target.endswith(".img"):
615 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700616 mount_point = "/" + target
617
Ying Wangf8824af2014-06-03 14:07:27 -0700618 fs_type = None
619 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700620 if info_dict["fstab"]:
Ethan Chenccc711c2014-06-02 16:49:59 -0700621 if mount_point == "/userdata_extra": mount_point = "/data"
622 if mount_point == "/userdata": mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700623 p = info_dict["fstab"][mount_point]
624 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800625 device = p.device
626 if "/" in device:
627 device = device[device.rfind("/")+1:]
628 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700629 if not fs_type or not limit:
630 return
Doug Zongkereef39442009-04-02 12:14:19 -0700631
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700632 if fs_type == "yaffs2":
633 # image size should be increased by 1/64th to account for the
634 # spare area (64 bytes per 2k page)
635 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800636 size = len(data)
637 pct = float(size) * 100.0 / limit
638 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
639 if pct >= 99.0:
640 raise ExternalError(msg)
641 elif pct >= 95.0:
642 print
643 print " WARNING: ", msg
644 print
645 elif OPTIONS.verbose:
646 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700647
648
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800649def ReadApkCerts(tf_zip):
650 """Given a target_files ZipFile, parse the META/apkcerts.txt file
651 and return a {package: cert} dict."""
652 certmap = {}
653 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
654 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700655 if not line:
656 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800657 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
658 r'private_key="(.*)"$', line)
659 if m:
660 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700661 public_key_suffix_len = len(OPTIONS.public_key_suffix)
662 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800663 if cert in SPECIAL_CERT_STRINGS and not privkey:
664 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700665 elif (cert.endswith(OPTIONS.public_key_suffix) and
666 privkey.endswith(OPTIONS.private_key_suffix) and
667 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
668 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800669 else:
670 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
671 return certmap
672
673
Doug Zongkereef39442009-04-02 12:14:19 -0700674COMMON_DOCSTRING = """
675 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700676 Prepend <dir>/bin to the list of places to search for binaries
677 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700678
Doug Zongker05d3dea2009-06-22 11:32:31 -0700679 -s (--device_specific) <file>
680 Path to the python module containing device-specific
681 releasetools code.
682
Doug Zongker8bec09e2009-11-30 15:37:14 -0800683 -x (--extra) <key=value>
684 Add a key/value pair to the 'extras' dict, which device-specific
685 extension code may look at.
686
Doug Zongkereef39442009-04-02 12:14:19 -0700687 -v (--verbose)
688 Show command lines being executed.
689
690 -h (--help)
691 Display this usage message and exit.
692"""
693
694def Usage(docstring):
695 print docstring.rstrip("\n")
696 print COMMON_DOCSTRING
697
698
699def ParseOptions(argv,
700 docstring,
701 extra_opts="", extra_long_opts=(),
702 extra_option_handler=None):
703 """Parse the options in argv and return any arguments that aren't
704 flags. docstring is the calling module's docstring, to be displayed
705 for errors and -h. extra_opts and extra_long_opts are for flags
706 defined by the caller, which are processed by passing them to
707 extra_option_handler."""
708
709 try:
710 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800711 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700712 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700713 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700714 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
715 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800716 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700717 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700718 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700719 Usage(docstring)
720 print "**", str(err), "**"
721 sys.exit(2)
722
Doug Zongkereef39442009-04-02 12:14:19 -0700723 for o, a in opts:
724 if o in ("-h", "--help"):
725 Usage(docstring)
726 sys.exit()
727 elif o in ("-v", "--verbose"):
728 OPTIONS.verbose = True
729 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700730 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700731 elif o in ("--signapk_path",):
732 OPTIONS.signapk_path = a
733 elif o in ("--extra_signapk_args",):
734 OPTIONS.extra_signapk_args = shlex.split(a)
735 elif o in ("--java_path",):
736 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700737 elif o in ("--java_args",):
738 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700739 elif o in ("--public_key_suffix",):
740 OPTIONS.public_key_suffix = a
741 elif o in ("--private_key_suffix",):
742 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800743 elif o in ("--boot_signer_path",):
744 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700745 elif o in ("--boot_signer_args",):
746 OPTIONS.boot_signer_args = shlex.split(a)
747 elif o in ("--verity_signer_path",):
748 OPTIONS.verity_signer_path = a
749 elif o in ("--verity_signer_args",):
750 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700751 elif o in ("-s", "--device_specific"):
752 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800753 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800754 key, value = a.split("=", 1)
755 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700756 else:
757 if extra_option_handler is None or not extra_option_handler(o, a):
758 assert False, "unknown option \"%s\"" % (o,)
759
Doug Zongker85448772014-09-09 14:59:20 -0700760 if OPTIONS.search_path:
761 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
762 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700763
764 return args
765
766
Doug Zongkerfc44a512014-08-26 13:10:25 -0700767def MakeTempFile(prefix=None, suffix=None):
768 """Make a temp file and add it to the list of things to be deleted
769 when Cleanup() is called. Return the filename."""
770 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
771 os.close(fd)
772 OPTIONS.tempfiles.append(fn)
773 return fn
774
775
Doug Zongkereef39442009-04-02 12:14:19 -0700776def Cleanup():
777 for i in OPTIONS.tempfiles:
778 if os.path.isdir(i):
779 shutil.rmtree(i)
780 else:
781 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700782
783
784class PasswordManager(object):
785 def __init__(self):
786 self.editor = os.getenv("EDITOR", None)
787 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
788
789 def GetPasswords(self, items):
790 """Get passwords corresponding to each string in 'items',
791 returning a dict. (The dict may have keys in addition to the
792 values in 'items'.)
793
794 Uses the passwords in $ANDROID_PW_FILE if available, letting the
795 user edit that file to add more needed passwords. If no editor is
796 available, or $ANDROID_PW_FILE isn't define, prompts the user
797 interactively in the ordinary way.
798 """
799
800 current = self.ReadFile()
801
802 first = True
803 while True:
804 missing = []
805 for i in items:
806 if i not in current or not current[i]:
807 missing.append(i)
808 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700809 if not missing:
810 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700811
812 for i in missing:
813 current[i] = ""
814
815 if not first:
816 print "key file %s still missing some passwords." % (self.pwfile,)
817 answer = raw_input("try to edit again? [y]> ").strip()
818 if answer and answer[0] not in 'yY':
819 raise RuntimeError("key passwords unavailable")
820 first = False
821
822 current = self.UpdateAndReadFile(current)
823
Dan Albert8b72aef2015-03-23 19:13:21 -0700824 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700825 """Prompt the user to enter a value (password) for each key in
826 'current' whose value is fales. Returns a new dict with all the
827 values.
828 """
829 result = {}
830 for k, v in sorted(current.iteritems()):
831 if v:
832 result[k] = v
833 else:
834 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700835 result[k] = getpass.getpass(
836 "Enter password for %s key> " % k).strip()
837 if result[k]:
838 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700839 return result
840
841 def UpdateAndReadFile(self, current):
842 if not self.editor or not self.pwfile:
843 return self.PromptResult(current)
844
845 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700846 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700847 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
848 f.write("# (Additional spaces are harmless.)\n\n")
849
850 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700851 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
852 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700853 f.write("[[[ %s ]]] %s\n" % (v, k))
854 if not v and first_line is None:
855 # position cursor on first line with no password.
856 first_line = i + 4
857 f.close()
858
859 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
860 _, _ = p.communicate()
861
862 return self.ReadFile()
863
864 def ReadFile(self):
865 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700866 if self.pwfile is None:
867 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 try:
869 f = open(self.pwfile, "r")
870 for line in f:
871 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700872 if not line or line[0] == '#':
873 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700874 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
875 if not m:
876 print "failed to parse password file: ", line
877 else:
878 result[m.group(2)] = m.group(1)
879 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700881 if e.errno != errno.ENOENT:
882 print "error reading password file: ", str(e)
883 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700884
885
Dan Albert8e0178d2015-01-27 15:53:15 -0800886def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
887 compress_type=None):
888 import datetime
889
890 # http://b/18015246
891 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
892 # for files larger than 2GiB. We can work around this by adjusting their
893 # limit. Note that `zipfile.writestr()` will not work for strings larger than
894 # 2GiB. The Python interpreter sometimes rejects strings that large (though
895 # it isn't clear to me exactly what circumstances cause this).
896 # `zipfile.write()` must be used directly to work around this.
897 #
898 # This mess can be avoided if we port to python3.
899 saved_zip64_limit = zipfile.ZIP64_LIMIT
900 zipfile.ZIP64_LIMIT = (1 << 32) - 1
901
902 if compress_type is None:
903 compress_type = zip_file.compression
904 if arcname is None:
905 arcname = filename
906
907 saved_stat = os.stat(filename)
908
909 try:
910 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
911 # file to be zipped and reset it when we're done.
912 os.chmod(filename, perms)
913
914 # Use a fixed timestamp so the output is repeatable.
915 epoch = datetime.datetime.fromtimestamp(0)
916 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
917 os.utime(filename, (timestamp, timestamp))
918
919 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
920 finally:
921 os.chmod(filename, saved_stat.st_mode)
922 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
923 zipfile.ZIP64_LIMIT = saved_zip64_limit
924
925
Tao Bao58c1b962015-05-20 09:32:18 -0700926def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700927 compress_type=None):
928 """Wrap zipfile.writestr() function to work around the zip64 limit.
929
930 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
931 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
932 when calling crc32(bytes).
933
934 But it still works fine to write a shorter string into a large zip file.
935 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
936 when we know the string won't be too long.
937 """
938
939 saved_zip64_limit = zipfile.ZIP64_LIMIT
940 zipfile.ZIP64_LIMIT = (1 << 32) - 1
941
942 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
943 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700944 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700945 if perms is None:
946 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800947 else:
Tao Baof3282b42015-04-01 11:21:55 -0700948 zinfo = zinfo_or_arcname
949
950 # If compress_type is given, it overrides the value in zinfo.
951 if compress_type is not None:
952 zinfo.compress_type = compress_type
953
Tao Bao58c1b962015-05-20 09:32:18 -0700954 # If perms is given, it has a priority.
955 if perms is not None:
956 zinfo.external_attr = perms << 16
957
Tao Baof3282b42015-04-01 11:21:55 -0700958 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700959 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
960
Dan Albert8b72aef2015-03-23 19:13:21 -0700961 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700962 zipfile.ZIP64_LIMIT = saved_zip64_limit
963
964
965def ZipClose(zip_file):
966 # http://b/18015246
967 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
968 # central directory.
969 saved_zip64_limit = zipfile.ZIP64_LIMIT
970 zipfile.ZIP64_LIMIT = (1 << 32) - 1
971
972 zip_file.close()
973
974 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700975
976
977class DeviceSpecificParams(object):
978 module = None
979 def __init__(self, **kwargs):
980 """Keyword arguments to the constructor become attributes of this
981 object, which is passed to all functions in the device-specific
982 module."""
983 for k, v in kwargs.iteritems():
984 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800985 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700986
987 if self.module is None:
988 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700989 if not path:
990 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700991 try:
992 if os.path.isdir(path):
993 info = imp.find_module("releasetools", [path])
994 else:
995 d, f = os.path.split(path)
996 b, x = os.path.splitext(f)
997 if x == ".py":
998 f = b
999 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001000 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001001 self.module = imp.load_module("device_specific", *info)
1002 except ImportError:
1003 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001004
1005 def _DoCall(self, function_name, *args, **kwargs):
1006 """Call the named function in the device-specific module, passing
1007 the given args and kwargs. The first argument to the call will be
1008 the DeviceSpecific object itself. If there is no module, or the
1009 module does not define the function, return the value of the
1010 'default' kwarg (which itself defaults to None)."""
1011 if self.module is None or not hasattr(self.module, function_name):
1012 return kwargs.get("default", None)
1013 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1014
1015 def FullOTA_Assertions(self):
1016 """Called after emitting the block of assertions at the top of a
1017 full OTA package. Implementations can add whatever additional
1018 assertions they like."""
1019 return self._DoCall("FullOTA_Assertions")
1020
Doug Zongkere5ff5902012-01-17 10:55:37 -08001021 def FullOTA_InstallBegin(self):
1022 """Called at the start of full OTA installation."""
1023 return self._DoCall("FullOTA_InstallBegin")
1024
Doug Zongker05d3dea2009-06-22 11:32:31 -07001025 def FullOTA_InstallEnd(self):
1026 """Called at the end of full OTA installation; typically this is
1027 used to install the image for the device's baseband processor."""
1028 return self._DoCall("FullOTA_InstallEnd")
1029
M1chad27ccf72014-11-25 15:30:48 +01001030 def FullOTA_PostValidate(self):
1031 """Called after installing and validating /system; typically this is
1032 used to resize the system partition after a block based installation."""
1033 return self._DoCall("FullOTA_PostValidate")
1034
Doug Zongker05d3dea2009-06-22 11:32:31 -07001035 def IncrementalOTA_Assertions(self):
1036 """Called after emitting the block of assertions at the top of an
1037 incremental OTA package. Implementations can add whatever
1038 additional assertions they like."""
1039 return self._DoCall("IncrementalOTA_Assertions")
1040
Doug Zongkere5ff5902012-01-17 10:55:37 -08001041 def IncrementalOTA_VerifyBegin(self):
1042 """Called at the start of the verification phase of incremental
1043 OTA installation; additional checks can be placed here to abort
1044 the script before any changes are made."""
1045 return self._DoCall("IncrementalOTA_VerifyBegin")
1046
Doug Zongker05d3dea2009-06-22 11:32:31 -07001047 def IncrementalOTA_VerifyEnd(self):
1048 """Called at the end of the verification phase of incremental OTA
1049 installation; additional checks can be placed here to abort the
1050 script before any changes are made."""
1051 return self._DoCall("IncrementalOTA_VerifyEnd")
1052
Doug Zongkere5ff5902012-01-17 10:55:37 -08001053 def IncrementalOTA_InstallBegin(self):
1054 """Called at the start of incremental OTA installation (after
1055 verification is complete)."""
1056 return self._DoCall("IncrementalOTA_InstallBegin")
1057
Doug Zongker05d3dea2009-06-22 11:32:31 -07001058 def IncrementalOTA_InstallEnd(self):
1059 """Called at the end of incremental OTA installation; typically
1060 this is used to install the image for the device's baseband
1061 processor."""
1062 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001063
1064class File(object):
1065 def __init__(self, name, data):
1066 self.name = name
1067 self.data = data
1068 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001069 self.sha1 = sha1(data).hexdigest()
1070
1071 @classmethod
1072 def FromLocalFile(cls, name, diskname):
1073 f = open(diskname, "rb")
1074 data = f.read()
1075 f.close()
1076 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001077
1078 def WriteToTemp(self):
1079 t = tempfile.NamedTemporaryFile()
1080 t.write(self.data)
1081 t.flush()
1082 return t
1083
Geremy Condra36bd3652014-02-06 19:45:10 -08001084 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001085 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001086
1087DIFF_PROGRAM_BY_EXT = {
1088 ".gz" : "imgdiff",
1089 ".zip" : ["imgdiff", "-z"],
1090 ".jar" : ["imgdiff", "-z"],
1091 ".apk" : ["imgdiff", "-z"],
1092 ".img" : "imgdiff",
1093 }
1094
1095class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001096 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001097 self.tf = tf
1098 self.sf = sf
1099 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001100 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001101
1102 def ComputePatch(self):
1103 """Compute the patch (as a string of data) needed to turn sf into
1104 tf. Returns the same tuple as GetPatch()."""
1105
1106 tf = self.tf
1107 sf = self.sf
1108
Doug Zongker24cd2802012-08-14 16:36:15 -07001109 if self.diff_program:
1110 diff_program = self.diff_program
1111 else:
1112 ext = os.path.splitext(tf.name)[1]
1113 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001114
1115 ttemp = tf.WriteToTemp()
1116 stemp = sf.WriteToTemp()
1117
1118 ext = os.path.splitext(tf.name)[1]
1119
1120 try:
1121 ptemp = tempfile.NamedTemporaryFile()
1122 if isinstance(diff_program, list):
1123 cmd = copy.copy(diff_program)
1124 else:
1125 cmd = [diff_program]
1126 cmd.append(stemp.name)
1127 cmd.append(ttemp.name)
1128 cmd.append(ptemp.name)
1129 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001130 err = []
1131 def run():
1132 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if e:
1134 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001135 th = threading.Thread(target=run)
1136 th.start()
1137 th.join(timeout=300) # 5 mins
1138 if th.is_alive():
1139 print "WARNING: diff command timed out"
1140 p.terminate()
1141 th.join(5)
1142 if th.is_alive():
1143 p.kill()
1144 th.join()
1145
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001146 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001147 print "WARNING: failure running %s:\n%s\n" % (
1148 diff_program, "".join(err))
1149 self.patch = None
1150 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001151 diff = ptemp.read()
1152 finally:
1153 ptemp.close()
1154 stemp.close()
1155 ttemp.close()
1156
1157 self.patch = diff
1158 return self.tf, self.sf, self.patch
1159
1160
1161 def GetPatch(self):
1162 """Return a tuple (target_file, source_file, patch_data).
1163 patch_data may be None if ComputePatch hasn't been called, or if
1164 computing the patch failed."""
1165 return self.tf, self.sf, self.patch
1166
1167
1168def ComputeDifferences(diffs):
1169 """Call ComputePatch on all the Difference objects in 'diffs'."""
1170 print len(diffs), "diffs to compute"
1171
1172 # Do the largest files first, to try and reduce the long-pole effect.
1173 by_size = [(i.tf.size, i) for i in diffs]
1174 by_size.sort(reverse=True)
1175 by_size = [i[1] for i in by_size]
1176
1177 lock = threading.Lock()
1178 diff_iter = iter(by_size) # accessed under lock
1179
1180 def worker():
1181 try:
1182 lock.acquire()
1183 for d in diff_iter:
1184 lock.release()
1185 start = time.time()
1186 d.ComputePatch()
1187 dur = time.time() - start
1188 lock.acquire()
1189
1190 tf, sf, patch = d.GetPatch()
1191 if sf.name == tf.name:
1192 name = tf.name
1193 else:
1194 name = "%s (%s)" % (tf.name, sf.name)
1195 if patch is None:
1196 print "patching failed! %s" % (name,)
1197 else:
1198 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1199 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1200 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001201 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001202 print e
1203 raise
1204
1205 # start worker threads; wait for them all to finish.
1206 threads = [threading.Thread(target=worker)
1207 for i in range(OPTIONS.worker_threads)]
1208 for th in threads:
1209 th.start()
1210 while threads:
1211 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001212
1213
Dan Albert8b72aef2015-03-23 19:13:21 -07001214class BlockDifference(object):
1215 def __init__(self, partition, tgt, src=None, check_first_block=False,
1216 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001217 self.tgt = tgt
1218 self.src = src
1219 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001220 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001221
Tao Bao5ece99d2015-05-12 11:42:31 -07001222 # Due to http://b/20939131, check_first_block is disabled temporarily.
1223 assert not self.check_first_block
1224
Tao Baodd2a5892015-03-12 12:32:37 -07001225 if version is None:
1226 version = 1
1227 if OPTIONS.info_dict:
1228 version = max(
1229 int(i) for i in
1230 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1231 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001232
1233 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001234 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001235 tmpdir = tempfile.mkdtemp()
1236 OPTIONS.tempfiles.append(tmpdir)
1237 self.path = os.path.join(tmpdir, partition)
1238 b.Compute(self.path)
1239
1240 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1241
1242 def WriteScript(self, script, output_zip, progress=None):
1243 if not self.src:
1244 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001245 script.Print("Patching %s image unconditionally..." % (self.partition,))
1246 else:
1247 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001248
Dan Albert8b72aef2015-03-23 19:13:21 -07001249 if progress:
1250 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001251 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001252 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001253
1254 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001255 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001256 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001257 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001258 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001259 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1260 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001261 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001262 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1263 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001264 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001265 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001266 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001267 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001268 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001269 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001270 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001271 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001272 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001273
Tao Baodd2a5892015-03-12 12:32:37 -07001274 # When generating incrementals for the system and vendor partitions,
1275 # explicitly check the first block (which contains the superblock) of
1276 # the partition to see if it's what we expect. If this check fails,
1277 # give an explicit log message about the partition having been
1278 # remounted R/W (the most likely explanation) and the need to flash to
1279 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001280 if self.check_first_block:
1281 self._CheckFirstBlock(script)
1282
Tao Baodd2a5892015-03-12 12:32:37 -07001283 # Abort the OTA update. Note that the incremental OTA cannot be applied
1284 # even if it may match the checksum of the target partition.
1285 # a) If version < 3, operations like move and erase will make changes
1286 # unconditionally and damage the partition.
1287 # b) If version >= 3, it won't even reach here.
1288 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1289 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001290
Tao Bao5fcaaef2015-06-01 13:40:49 -07001291 def _WritePostInstallVerifyScript(self, script):
1292 partition = self.partition
1293 script.Print('Verifying the updated %s image...' % (partition,))
1294 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1295 ranges = self.tgt.care_map
1296 ranges_str = ranges.to_string_raw()
1297 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1298 self.device, ranges_str,
1299 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001300
1301 # Bug: 20881595
1302 # Verify that extended blocks are really zeroed out.
1303 if self.tgt.extended:
1304 ranges_str = self.tgt.extended.to_string_raw()
1305 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1306 self.device, ranges_str,
1307 self._HashZeroBlocks(self.tgt.extended.size())))
1308 script.Print('Verified the updated %s image.' % (partition,))
1309 script.AppendExtra(
1310 'else\n'
1311 ' abort("%s partition has unexpected non-zero contents after OTA '
1312 'update");\n'
1313 'endif;' % (partition,))
1314 else:
1315 script.Print('Verified the updated %s image.' % (partition,))
1316
Tao Bao5fcaaef2015-06-01 13:40:49 -07001317 script.AppendExtra(
1318 'else\n'
1319 ' abort("%s partition has unexpected contents after OTA update");\n'
1320 'endif;' % (partition,))
1321
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001322 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001323 ZipWrite(output_zip,
1324 '{}.transfer.list'.format(self.path),
1325 '{}.transfer.list'.format(self.partition))
1326 ZipWrite(output_zip,
1327 '{}.new.dat'.format(self.path),
1328 '{}.new.dat'.format(self.partition))
1329 ZipWrite(output_zip,
1330 '{}.patch.dat'.format(self.path),
1331 '{}.patch.dat'.format(self.partition),
1332 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001333
Dan Albert8e0178d2015-01-27 15:53:15 -08001334 call = ('block_image_update("{device}", '
1335 'package_extract_file("{partition}.transfer.list"), '
1336 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1337 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001339
Dan Albert8b72aef2015-03-23 19:13:21 -07001340 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001341 data = source.ReadRangeSet(ranges)
1342 ctx = sha1()
1343
1344 for p in data:
1345 ctx.update(p)
1346
1347 return ctx.hexdigest()
1348
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001349 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1350 """Return the hash value for all zero blocks."""
1351 zero_block = '\x00' * 4096
1352 ctx = sha1()
1353 for _ in range(num_blocks):
1354 ctx.update(zero_block)
1355
1356 return ctx.hexdigest()
1357
Tao Bao5ece99d2015-05-12 11:42:31 -07001358 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1359 # remounting R/W. Will change the checking to a finer-grained way to
1360 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001361 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001362 r = rangelib.RangeSet((0, 1))
1363 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001364
1365 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1366 'abort("%s has been remounted R/W; '
1367 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001368 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001369 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001370
1371DataImage = blockimgdiff.DataImage
1372
1373
Doug Zongker96a57e72010-09-26 14:57:41 -07001374# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001375PARTITION_TYPES = {
1376 "yaffs2": "MTD",
1377 "mtd": "MTD",
1378 "ext4": "EMMC",
1379 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001380 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001381 "squashfs": "EMMC",
1382 "ext2": "EMMC",
1383 "ext3": "EMMC",
Stephen Bird9f657582015-10-08 02:05:06 -07001384 "vfat": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001385}
Doug Zongker96a57e72010-09-26 14:57:41 -07001386
1387def GetTypeAndDevice(mount_point, info):
1388 fstab = info["fstab"]
1389 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001390 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1391 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001392 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001393 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001394
1395
1396def ParseCertificate(data):
1397 """Parse a PEM-format certificate."""
1398 cert = []
1399 save = False
1400 for line in data.split("\n"):
1401 if "--END CERTIFICATE--" in line:
1402 break
1403 if save:
1404 cert.append(line)
1405 if "--BEGIN CERTIFICATE--" in line:
1406 save = True
1407 cert = "".join(cert).decode('base64')
1408 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001409
Doug Zongker412c02f2014-02-13 10:58:24 -08001410def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1411 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001412 """Generate a binary patch that creates the recovery image starting
1413 with the boot image. (Most of the space in these images is just the
1414 kernel, which is identical for the two, so the resulting patch
1415 should be efficient.) Add it to the output zip, along with a shell
1416 script that is run from init.rc on first boot to actually do the
1417 patching and install the new recovery image.
1418
1419 recovery_img and boot_img should be File objects for the
1420 corresponding images. info should be the dictionary returned by
1421 common.LoadInfoDict() on the input target_files.
1422 """
1423
Doug Zongker412c02f2014-02-13 10:58:24 -08001424 if info_dict is None:
1425 info_dict = OPTIONS.info_dict
1426
Doug Zongkerc9253822014-02-04 12:17:58 -08001427 diff_program = ["imgdiff"]
1428 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1429 if os.path.exists(path):
1430 diff_program.append("-b")
1431 diff_program.append(path)
1432 bonus_args = "-b /system/etc/recovery-resource.dat"
1433 else:
1434 bonus_args = ""
1435
1436 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1437 _, _, patch = d.ComputePatch()
1438 output_sink("recovery-from-boot.p", patch)
1439
Dan Albertebb19aa2015-03-27 19:11:53 -07001440 try:
1441 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1442 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1443 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001444 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001445
1446 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001447if [ -f /system/etc/recovery-transform.sh ]; then
1448 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1449fi
1450
Doug Zongkerc9253822014-02-04 12:17:58 -08001451if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1452 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"
1453else
1454 log -t recovery "Recovery image already installed"
1455fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001456""" % {'boot_size': boot_img.size,
1457 'boot_sha1': boot_img.sha1,
1458 'recovery_size': recovery_img.size,
1459 'recovery_sha1': recovery_img.sha1,
1460 'boot_type': boot_type,
1461 'boot_device': boot_device,
1462 'recovery_type': recovery_type,
1463 'recovery_device': recovery_device,
1464 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001465
1466 # The install script location moved from /system/etc to /system/bin
1467 # in the L release. Parse the init.rc file to find out where the
1468 # target-files expects it to be, and put it there.
1469 sh_location = "etc/install-recovery.sh"
1470 try:
1471 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1472 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001473 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001474 if m:
1475 sh_location = m.group(1)
1476 print "putting script in", sh_location
1477 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001478 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001479 print "failed to read init.rc: %s" % (e,)
1480
1481 output_sink(sh_location, sh)