blob: 47cf7594130ce05653e566e9c1de109642e5e6bc [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
Tao Baoe09359a2015-10-13 16:37:12 -070062 self.source_info_dict = None
63 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.worker_threads = None
65
66
67OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070068
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080069
70# Values for "certificate" in apkcerts that mean special things.
71SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
72
Tao Baoe9647142015-08-07 19:49:45 -070073# Stash size cannot exceed cache_size * threshold.
74OPTIONS.cache_size = None
75OPTIONS.stash_threshold = 0.8
76
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080077
Dan Albert8b72aef2015-03-23 19:13:21 -070078class ExternalError(RuntimeError):
79 pass
Doug Zongkereef39442009-04-02 12:14:19 -070080
81
82def Run(args, **kwargs):
83 """Create and return a subprocess.Popen object, printing the command
84 line on the terminal if -v was specified."""
85 if OPTIONS.verbose:
86 print " running: ", " ".join(args)
87 return subprocess.Popen(args, **kwargs)
88
89
Ying Wang7e6d4e42010-12-13 16:25:36 -080090def CloseInheritedPipes():
91 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
92 before doing other work."""
93 if platform.system() != "Darwin":
94 return
95 for d in range(3, 1025):
96 try:
97 stat = os.fstat(d)
98 if stat is not None:
99 pipebit = stat[0] & 0x1000
100 if pipebit != 0:
101 os.close(d)
102 except OSError:
103 pass
104
105
Dan Albert8b72aef2015-03-23 19:13:21 -0700106def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700107 """Read and parse the META/misc_info.txt key/value pairs from the
108 input target files and return a dict."""
109
Doug Zongkerc9253822014-02-04 12:17:58 -0800110 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 if isinstance(input_file, zipfile.ZipFile):
112 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800113 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700114 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800115 try:
116 with open(path) as f:
117 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700118 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800119 if e.errno == errno.ENOENT:
120 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700121 d = {}
122 try:
Michael Runge6e836112014-04-15 17:40:21 -0700123 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700124 except KeyError:
125 # ok if misc_info.txt doesn't exist
126 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700127
Doug Zongker37974732010-09-16 17:44:38 -0700128 # backwards compatibility: These values used to be in their own
129 # files. Look for them, in case we're processing an old
130 # target_files zip.
131
132 if "mkyaffs2_extra_flags" not in d:
133 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700134 d["mkyaffs2_extra_flags"] = read_helper(
135 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700136 except KeyError:
137 # ok if flags don't exist
138 pass
139
140 if "recovery_api_version" not in d:
141 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700142 d["recovery_api_version"] = read_helper(
143 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700144 except KeyError:
145 raise ValueError("can't find recovery API version in input target-files")
146
147 if "tool_extensions" not in d:
148 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800149 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700150 except KeyError:
151 # ok if extensions don't exist
152 pass
153
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800154 if "fstab_version" not in d:
155 d["fstab_version"] = "1"
156
Ameya Thakure5b5a272013-07-29 17:39:37 -0700157 if "device_type" not in d:
158 d["device_type"] = "MMC"
Doug Zongker37974732010-09-16 17:44:38 -0700159 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800160 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700161 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700162 if not line:
163 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700164 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700165 if not value:
166 continue
Doug Zongker37974732010-09-16 17:44:38 -0700167 if name == "blocksize":
168 d[name] = value
169 else:
170 d[name + "_size"] = value
171 except KeyError:
172 pass
173
174 def makeint(key):
175 if key in d:
176 d[key] = int(d[key], 0)
177
178 makeint("recovery_api_version")
179 makeint("blocksize")
180 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700181 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700182 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700183 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700184 makeint("recovery_size")
185 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800186 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700187
Ameya Thakure5b5a272013-07-29 17:39:37 -0700188 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d["device_type"])
Doug Zongkerc9253822014-02-04 12:17:58 -0800189 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700190 return d
191
Doug Zongkerc9253822014-02-04 12:17:58 -0800192def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700193 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800194 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700195 except KeyError:
196 print "Warning: could not find SYSTEM/build.prop in %s" % zip
197 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700198 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700199
Michael Runge6e836112014-04-15 17:40:21 -0700200def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700201 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700202 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700203 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700204 if not line or line.startswith("#"):
205 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700206 if "=" in line:
207 name, value = line.split("=", 1)
208 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700209 return d
210
Ameya Thakure5b5a272013-07-29 17:39:37 -0700211def LoadRecoveryFSTab(read_helper, fstab_version, type):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700212 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700213 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700214 self.mount_point = mount_point
215 self.fs_type = fs_type
216 self.device = device
217 self.length = length
218 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700219 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700220
221 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800222 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700223 except KeyError:
Doug Zongkerc9253822014-02-04 12:17:58 -0800224 print "Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab"
Jeff Davidson033fbe22011-10-26 18:08:09 -0700225 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700226
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800227 if fstab_version == 1:
228 d = {}
229 for line in data.split("\n"):
230 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700231 if not line or line.startswith("#"):
232 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800233 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700234 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800235 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800236 options = None
237 if len(pieces) >= 4:
238 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700239 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800240 if len(pieces) >= 5:
241 options = pieces[4]
242 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800244 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800245 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700246 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700247
Dan Albert8b72aef2015-03-23 19:13:21 -0700248 mount_point = pieces[0]
249 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800250 if options:
251 options = options.split(",")
252 for i in options:
253 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700254 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800255 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 print "%s: unknown option \"%s\"" % (mount_point, i)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257
Dan Albert8b72aef2015-03-23 19:13:21 -0700258 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
259 device=pieces[2], length=length,
260 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800261
262 elif fstab_version == 2:
263 d = {}
264 for line in data.split("\n"):
265 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700266 if not line or line.startswith("#"):
267 continue
Tao Bao548eb762015-06-10 12:32:41 -0700268 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800269 pieces = line.split()
270 if len(pieces) != 5:
271 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
272
273 # Ignore entries that are managed by vold
274 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700275 if "voldmanaged=" in options:
276 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800277
278 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800280 options = options.split(",")
281 for i in options:
282 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800284 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800285 # Ignore all unknown options in the unified fstab
286 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800287
Tao Bao548eb762015-06-10 12:32:41 -0700288 mount_flags = pieces[3]
289 # Honor the SELinux context if present.
290 context = None
291 for i in mount_flags.split(","):
292 if i.startswith("context="):
293 context = i
294
Dan Albert8b72aef2015-03-23 19:13:21 -0700295 mount_point = pieces[1]
296 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700297 device=pieces[0], length=length,
298 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299
300 else:
301 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
302
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700303 return d
304
305
Doug Zongker37974732010-09-16 17:44:38 -0700306def DumpInfoDict(d):
307 for k, v in sorted(d.items()):
308 print "%-25s = (%s) %s" % (k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700309
Dan Albert8b72aef2015-03-23 19:13:21 -0700310
Doug Zongkerd5131602012-08-02 14:46:42 -0700311def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700312 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700313 'sourcedir'), and turn them into a boot image. Return the image
314 data, or None if sourcedir does not appear to contains files for
315 building the requested image."""
316
317 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
318 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
319 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700320
Doug Zongkerd5131602012-08-02 14:46:42 -0700321 if info_dict is None:
322 info_dict = OPTIONS.info_dict
323
Doug Zongkereef39442009-04-02 12:14:19 -0700324 ramdisk_img = tempfile.NamedTemporaryFile()
325 img = tempfile.NamedTemporaryFile()
326
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700327 if os.access(fs_config_file, os.F_OK):
328 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
329 else:
330 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
331 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700332 p2 = Run(["minigzip"],
333 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700334
335 p2.wait()
336 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700337 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
338 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700339
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000340 """check if uboot is requested"""
341 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200342 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000343 cmd = ["mkimage"]
344 for argument in open(fn).read().rstrip("\n").split(" "):
345 cmd.append(argument)
346 cmd.append("-d")
347 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
348 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200349
Tao Baod95e9fd2015-03-29 23:07:41 -0700350 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000351 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
352 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
353 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700354
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000355 fn = os.path.join(sourcedir, "second")
356 if os.access(fn, os.F_OK):
357 cmd.append("--second")
358 cmd.append(fn)
359
360 fn = os.path.join(sourcedir, "cmdline")
361 if os.access(fn, os.F_OK):
362 cmd.append("--cmdline")
363 cmd.append(open(fn).read().rstrip("\n"))
364
365 fn = os.path.join(sourcedir, "base")
366 if os.access(fn, os.F_OK):
367 cmd.append("--base")
368 cmd.append(open(fn).read().rstrip("\n"))
369
370 fn = os.path.join(sourcedir, "tagsaddr")
371 if os.access(fn, os.F_OK):
372 cmd.append("--tags-addr")
373 cmd.append(open(fn).read().rstrip("\n"))
374
Ameya Thakure5b5a272013-07-29 17:39:37 -0700375 fn = os.path.join(sourcedir, "tags_offset")
376 if os.access(fn, os.F_OK):
377 cmd.append("--tags_offset")
378 cmd.append(open(fn).read().rstrip("\n"))
379
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000380 fn = os.path.join(sourcedir, "ramdisk_offset")
381 if os.access(fn, os.F_OK):
382 cmd.append("--ramdisk_offset")
383 cmd.append(open(fn).read().rstrip("\n"))
384
385 fn = os.path.join(sourcedir, "dt_args")
386 if os.access(fn, os.F_OK):
387 cmd.append("--dt")
388 cmd.append(open(fn).read().rstrip("\n"))
389
390 fn = os.path.join(sourcedir, "pagesize")
391 if os.access(fn, os.F_OK):
392 cmd.append("--pagesize")
393 cmd.append(open(fn).read().rstrip("\n"))
394
395 args = info_dict.get("mkbootimg_args", None)
396 if args and args.strip():
397 cmd.extend(shlex.split(args))
398
399 img_unsigned = None
400 if info_dict.get("vboot", None):
401 img_unsigned = tempfile.NamedTemporaryFile()
402 cmd.extend(["--ramdisk", ramdisk_img.name,
403 "--output", img_unsigned.name])
404 else:
405 cmd.extend(["--ramdisk", ramdisk_img.name,
406 "--output", img.name])
407
Doug Zongker38a649f2009-06-17 09:07:09 -0700408 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700409 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700410 assert p.returncode == 0, "mkbootimg of %s image failed" % (
411 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700412
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100413 if (info_dict.get("boot_signer", None) == "true" and
414 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700415 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700416 cmd = [OPTIONS.boot_signer_path]
417 cmd.extend(OPTIONS.boot_signer_args)
418 cmd.extend([path, img.name,
419 info_dict["verity_key"] + ".pk8",
420 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700421 p = Run(cmd, stdout=subprocess.PIPE)
422 p.communicate()
423 assert p.returncode == 0, "boot_signer of %s image failed" % path
424
Tao Baod95e9fd2015-03-29 23:07:41 -0700425 # Sign the image if vboot is non-empty.
426 elif info_dict.get("vboot", None):
427 path = "/" + os.path.basename(sourcedir).lower()
428 img_keyblock = tempfile.NamedTemporaryFile()
429 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
430 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700431 info_dict["vboot_key"] + ".vbprivk",
432 info_dict["vboot_subkey"] + ".vbprivk",
433 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700434 img.name]
435 p = Run(cmd, stdout=subprocess.PIPE)
436 p.communicate()
437 assert p.returncode == 0, "vboot_signer of %s image failed" % path
438
Tao Baof3282b42015-04-01 11:21:55 -0700439 # Clean up the temp files.
440 img_unsigned.close()
441 img_keyblock.close()
442
Doug Zongkereef39442009-04-02 12:14:19 -0700443 img.seek(os.SEEK_SET, 0)
444 data = img.read()
445
446 ramdisk_img.close()
447 img.close()
448
449 return data
450
451
Doug Zongkerd5131602012-08-02 14:46:42 -0700452def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
453 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800454 """Return a File object (with name 'name') with the desired bootable
455 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700456 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
457 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800458 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700459
Doug Zongker55d93282011-01-25 17:03:34 -0800460 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
461 if os.path.exists(prebuilt_path):
Doug Zongker6f1d0312014-08-22 08:07:12 -0700462 print "using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,)
Doug Zongker55d93282011-01-25 17:03:34 -0800463 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700464
465 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
466 if os.path.exists(prebuilt_path):
467 print "using prebuilt %s from IMAGES..." % (prebuilt_name,)
468 return File.FromLocalFile(name, prebuilt_path)
469
470 print "building image from target_files %s..." % (tree_subdir,)
471 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
472 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
473 os.path.join(unpack_dir, fs_config),
474 info_dict)
475 if data:
476 return File(name, data)
477 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800478
Doug Zongkereef39442009-04-02 12:14:19 -0700479
Doug Zongker75f17362009-12-08 13:46:44 -0800480def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800481 """Unzip the given archive into a temporary directory and return the name.
482
483 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
484 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
485
486 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
487 main file), open for reading.
488 """
Doug Zongkereef39442009-04-02 12:14:19 -0700489
490 tmp = tempfile.mkdtemp(prefix="targetfiles-")
491 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800492
493 def unzip_to_dir(filename, dirname):
Dan Pasanen3d4039a2015-02-07 18:59:36 -0600494 subprocess.call(["rm", "-rf", dirname + filename, "targetfiles-*"])
Doug Zongker55d93282011-01-25 17:03:34 -0800495 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
496 if pattern is not None:
497 cmd.append(pattern)
498 p = Run(cmd, stdout=subprocess.PIPE)
499 p.communicate()
500 if p.returncode != 0:
501 raise ExternalError("failed to unzip input target-files \"%s\"" %
502 (filename,))
503
504 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
505 if m:
506 unzip_to_dir(m.group(1), tmp)
507 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
508 filename = m.group(1)
509 else:
510 unzip_to_dir(filename, tmp)
511
512 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700513
514
515def GetKeyPasswords(keylist):
516 """Given a list of keys, prompt the user to enter passwords for
517 those which require them. Return a {key: password} dict. password
518 will be None if the key has no password."""
519
Doug Zongker8ce7c252009-05-22 13:34:54 -0700520 no_passwords = []
521 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700522 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700523 devnull = open("/dev/null", "w+b")
524 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800525 # We don't need a password for things that aren't really keys.
526 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700527 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700528 continue
529
T.R. Fullhart37e10522013-03-18 10:31:26 -0700530 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700531 "-inform", "DER", "-nocrypt"],
532 stdin=devnull.fileno(),
533 stdout=devnull.fileno(),
534 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700535 p.communicate()
536 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700537 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700538 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700539 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700540 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
541 "-inform", "DER", "-passin", "pass:"],
542 stdin=devnull.fileno(),
543 stdout=devnull.fileno(),
544 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700545 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700546 if p.returncode == 0:
547 # Encrypted key with empty string as password.
548 key_passwords[k] = ''
549 elif stderr.startswith('Error decrypting key'):
550 # Definitely encrypted key.
551 # It would have said "Error reading key" if it didn't parse correctly.
552 need_passwords.append(k)
553 else:
554 # Potentially, a type of key that openssl doesn't understand.
555 # We'll let the routines in signapk.jar handle it.
556 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700557 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700558
T.R. Fullhart37e10522013-03-18 10:31:26 -0700559 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700560 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700561 return key_passwords
562
563
Doug Zongker951495f2009-08-14 12:44:19 -0700564def SignFile(input_name, output_name, key, password, align=None,
565 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700566 """Sign the input_name zip/jar/apk, producing output_name. Use the
567 given key and password (the latter may be None if the key does not
568 have a password.
569
570 If align is an integer > 1, zipalign is run to align stored files in
571 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700572
573 If whole_file is true, use the "-w" option to SignApk to embed a
574 signature that covers the whole file in the archive comment of the
575 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700576 """
Doug Zongker951495f2009-08-14 12:44:19 -0700577
Doug Zongkereef39442009-04-02 12:14:19 -0700578 if align == 0 or align == 1:
579 align = None
580
581 if align:
582 temp = tempfile.NamedTemporaryFile()
583 sign_name = temp.name
584 else:
585 sign_name = output_name
586
Baligh Uddin339ee492014-09-05 11:18:07 -0700587 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700588 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
589 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700590 if whole_file:
591 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700592 cmd.extend([key + OPTIONS.public_key_suffix,
593 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700594 input_name, sign_name])
595
596 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700597 if password is not None:
598 password += "\n"
599 p.communicate(password)
600 if p.returncode != 0:
601 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
602
603 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700604 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700605 p.communicate()
606 if p.returncode != 0:
607 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
608 temp.close()
609
610
Doug Zongker37974732010-09-16 17:44:38 -0700611def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700612 """Check the data string passed against the max size limit, if
613 any, for the given target. Raise exception if the data is too big.
614 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700615
Dan Albert8b72aef2015-03-23 19:13:21 -0700616 if target.endswith(".img"):
617 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700618 mount_point = "/" + target
619
Ying Wangf8824af2014-06-03 14:07:27 -0700620 fs_type = None
621 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700622 if info_dict["fstab"]:
Ethan Chenccc711c2014-06-02 16:49:59 -0700623 if mount_point == "/userdata_extra": mount_point = "/data"
624 if mount_point == "/userdata": mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700625 p = info_dict["fstab"][mount_point]
626 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800627 device = p.device
628 if "/" in device:
629 device = device[device.rfind("/")+1:]
630 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700631 if not fs_type or not limit:
632 return
Doug Zongkereef39442009-04-02 12:14:19 -0700633
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700634 if fs_type == "yaffs2":
635 # image size should be increased by 1/64th to account for the
636 # spare area (64 bytes per 2k page)
637 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800638 size = len(data)
639 pct = float(size) * 100.0 / limit
640 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
641 if pct >= 99.0:
642 raise ExternalError(msg)
643 elif pct >= 95.0:
644 print
645 print " WARNING: ", msg
646 print
647 elif OPTIONS.verbose:
648 print " ", msg
Doug Zongkereef39442009-04-02 12:14:19 -0700649
650
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800651def ReadApkCerts(tf_zip):
652 """Given a target_files ZipFile, parse the META/apkcerts.txt file
653 and return a {package: cert} dict."""
654 certmap = {}
655 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
656 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700657 if not line:
658 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800659 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
660 r'private_key="(.*)"$', line)
661 if m:
662 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700663 public_key_suffix_len = len(OPTIONS.public_key_suffix)
664 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800665 if cert in SPECIAL_CERT_STRINGS and not privkey:
666 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700667 elif (cert.endswith(OPTIONS.public_key_suffix) and
668 privkey.endswith(OPTIONS.private_key_suffix) and
669 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
670 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800671 else:
672 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
673 return certmap
674
675
Doug Zongkereef39442009-04-02 12:14:19 -0700676COMMON_DOCSTRING = """
677 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700678 Prepend <dir>/bin to the list of places to search for binaries
679 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700680
Doug Zongker05d3dea2009-06-22 11:32:31 -0700681 -s (--device_specific) <file>
682 Path to the python module containing device-specific
683 releasetools code.
684
Doug Zongker8bec09e2009-11-30 15:37:14 -0800685 -x (--extra) <key=value>
686 Add a key/value pair to the 'extras' dict, which device-specific
687 extension code may look at.
688
Doug Zongkereef39442009-04-02 12:14:19 -0700689 -v (--verbose)
690 Show command lines being executed.
691
692 -h (--help)
693 Display this usage message and exit.
694"""
695
696def Usage(docstring):
697 print docstring.rstrip("\n")
698 print COMMON_DOCSTRING
699
700
701def ParseOptions(argv,
702 docstring,
703 extra_opts="", extra_long_opts=(),
704 extra_option_handler=None):
705 """Parse the options in argv and return any arguments that aren't
706 flags. docstring is the calling module's docstring, to be displayed
707 for errors and -h. extra_opts and extra_long_opts are for flags
708 defined by the caller, which are processed by passing them to
709 extra_option_handler."""
710
711 try:
712 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800713 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700714 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700715 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700716 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
717 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800718 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700719 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700720 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700721 Usage(docstring)
722 print "**", str(err), "**"
723 sys.exit(2)
724
Doug Zongkereef39442009-04-02 12:14:19 -0700725 for o, a in opts:
726 if o in ("-h", "--help"):
727 Usage(docstring)
728 sys.exit()
729 elif o in ("-v", "--verbose"):
730 OPTIONS.verbose = True
731 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700732 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700733 elif o in ("--signapk_path",):
734 OPTIONS.signapk_path = a
735 elif o in ("--extra_signapk_args",):
736 OPTIONS.extra_signapk_args = shlex.split(a)
737 elif o in ("--java_path",):
738 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700739 elif o in ("--java_args",):
740 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700741 elif o in ("--public_key_suffix",):
742 OPTIONS.public_key_suffix = a
743 elif o in ("--private_key_suffix",):
744 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800745 elif o in ("--boot_signer_path",):
746 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700747 elif o in ("--boot_signer_args",):
748 OPTIONS.boot_signer_args = shlex.split(a)
749 elif o in ("--verity_signer_path",):
750 OPTIONS.verity_signer_path = a
751 elif o in ("--verity_signer_args",):
752 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700753 elif o in ("-s", "--device_specific"):
754 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800755 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800756 key, value = a.split("=", 1)
757 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700758 else:
759 if extra_option_handler is None or not extra_option_handler(o, a):
760 assert False, "unknown option \"%s\"" % (o,)
761
Doug Zongker85448772014-09-09 14:59:20 -0700762 if OPTIONS.search_path:
763 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
764 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700765
766 return args
767
768
Doug Zongkerfc44a512014-08-26 13:10:25 -0700769def MakeTempFile(prefix=None, suffix=None):
770 """Make a temp file and add it to the list of things to be deleted
771 when Cleanup() is called. Return the filename."""
772 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
773 os.close(fd)
774 OPTIONS.tempfiles.append(fn)
775 return fn
776
777
Doug Zongkereef39442009-04-02 12:14:19 -0700778def Cleanup():
779 for i in OPTIONS.tempfiles:
780 if os.path.isdir(i):
781 shutil.rmtree(i)
782 else:
783 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700784
785
786class PasswordManager(object):
787 def __init__(self):
788 self.editor = os.getenv("EDITOR", None)
789 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
790
791 def GetPasswords(self, items):
792 """Get passwords corresponding to each string in 'items',
793 returning a dict. (The dict may have keys in addition to the
794 values in 'items'.)
795
796 Uses the passwords in $ANDROID_PW_FILE if available, letting the
797 user edit that file to add more needed passwords. If no editor is
798 available, or $ANDROID_PW_FILE isn't define, prompts the user
799 interactively in the ordinary way.
800 """
801
802 current = self.ReadFile()
803
804 first = True
805 while True:
806 missing = []
807 for i in items:
808 if i not in current or not current[i]:
809 missing.append(i)
810 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700811 if not missing:
812 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700813
814 for i in missing:
815 current[i] = ""
816
817 if not first:
818 print "key file %s still missing some passwords." % (self.pwfile,)
819 answer = raw_input("try to edit again? [y]> ").strip()
820 if answer and answer[0] not in 'yY':
821 raise RuntimeError("key passwords unavailable")
822 first = False
823
824 current = self.UpdateAndReadFile(current)
825
Dan Albert8b72aef2015-03-23 19:13:21 -0700826 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700827 """Prompt the user to enter a value (password) for each key in
828 'current' whose value is fales. Returns a new dict with all the
829 values.
830 """
831 result = {}
832 for k, v in sorted(current.iteritems()):
833 if v:
834 result[k] = v
835 else:
836 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700837 result[k] = getpass.getpass(
838 "Enter password for %s key> " % k).strip()
839 if result[k]:
840 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700841 return result
842
843 def UpdateAndReadFile(self, current):
844 if not self.editor or not self.pwfile:
845 return self.PromptResult(current)
846
847 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700848 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700849 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
850 f.write("# (Additional spaces are harmless.)\n\n")
851
852 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
854 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700855 f.write("[[[ %s ]]] %s\n" % (v, k))
856 if not v and first_line is None:
857 # position cursor on first line with no password.
858 first_line = i + 4
859 f.close()
860
861 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
862 _, _ = p.communicate()
863
864 return self.ReadFile()
865
866 def ReadFile(self):
867 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700868 if self.pwfile is None:
869 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700870 try:
871 f = open(self.pwfile, "r")
872 for line in f:
873 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700874 if not line or line[0] == '#':
875 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700876 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
877 if not m:
878 print "failed to parse password file: ", line
879 else:
880 result[m.group(2)] = m.group(1)
881 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700882 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700883 if e.errno != errno.ENOENT:
884 print "error reading password file: ", str(e)
885 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700886
887
Dan Albert8e0178d2015-01-27 15:53:15 -0800888def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
889 compress_type=None):
890 import datetime
891
892 # http://b/18015246
893 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
894 # for files larger than 2GiB. We can work around this by adjusting their
895 # limit. Note that `zipfile.writestr()` will not work for strings larger than
896 # 2GiB. The Python interpreter sometimes rejects strings that large (though
897 # it isn't clear to me exactly what circumstances cause this).
898 # `zipfile.write()` must be used directly to work around this.
899 #
900 # This mess can be avoided if we port to python3.
901 saved_zip64_limit = zipfile.ZIP64_LIMIT
902 zipfile.ZIP64_LIMIT = (1 << 32) - 1
903
904 if compress_type is None:
905 compress_type = zip_file.compression
906 if arcname is None:
907 arcname = filename
908
909 saved_stat = os.stat(filename)
910
911 try:
912 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
913 # file to be zipped and reset it when we're done.
914 os.chmod(filename, perms)
915
916 # Use a fixed timestamp so the output is repeatable.
917 epoch = datetime.datetime.fromtimestamp(0)
918 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
919 os.utime(filename, (timestamp, timestamp))
920
921 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
922 finally:
923 os.chmod(filename, saved_stat.st_mode)
924 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
925 zipfile.ZIP64_LIMIT = saved_zip64_limit
926
927
Tao Bao58c1b962015-05-20 09:32:18 -0700928def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700929 compress_type=None):
930 """Wrap zipfile.writestr() function to work around the zip64 limit.
931
932 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
933 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
934 when calling crc32(bytes).
935
936 But it still works fine to write a shorter string into a large zip file.
937 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
938 when we know the string won't be too long.
939 """
940
941 saved_zip64_limit = zipfile.ZIP64_LIMIT
942 zipfile.ZIP64_LIMIT = (1 << 32) - 1
943
944 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
945 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700946 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700947 if perms is None:
948 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800949 else:
Tao Baof3282b42015-04-01 11:21:55 -0700950 zinfo = zinfo_or_arcname
951
952 # If compress_type is given, it overrides the value in zinfo.
953 if compress_type is not None:
954 zinfo.compress_type = compress_type
955
Tao Bao58c1b962015-05-20 09:32:18 -0700956 # If perms is given, it has a priority.
957 if perms is not None:
958 zinfo.external_attr = perms << 16
959
Tao Baof3282b42015-04-01 11:21:55 -0700960 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700961 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
962
Dan Albert8b72aef2015-03-23 19:13:21 -0700963 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700964 zipfile.ZIP64_LIMIT = saved_zip64_limit
965
966
967def ZipClose(zip_file):
968 # http://b/18015246
969 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
970 # central directory.
971 saved_zip64_limit = zipfile.ZIP64_LIMIT
972 zipfile.ZIP64_LIMIT = (1 << 32) - 1
973
974 zip_file.close()
975
976 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700977
978
979class DeviceSpecificParams(object):
980 module = None
981 def __init__(self, **kwargs):
982 """Keyword arguments to the constructor become attributes of this
983 object, which is passed to all functions in the device-specific
984 module."""
985 for k, v in kwargs.iteritems():
986 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800987 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700988
989 if self.module is None:
990 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -0700991 if not path:
992 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700993 try:
994 if os.path.isdir(path):
995 info = imp.find_module("releasetools", [path])
996 else:
997 d, f = os.path.split(path)
998 b, x = os.path.splitext(f)
999 if x == ".py":
1000 f = b
1001 info = imp.find_module(f, [d])
Doug Zongkereb0a78a2014-01-27 10:01:06 -08001002 print "loaded device-specific extensions from", path
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001003 self.module = imp.load_module("device_specific", *info)
1004 except ImportError:
1005 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -07001006
1007 def _DoCall(self, function_name, *args, **kwargs):
1008 """Call the named function in the device-specific module, passing
1009 the given args and kwargs. The first argument to the call will be
1010 the DeviceSpecific object itself. If there is no module, or the
1011 module does not define the function, return the value of the
1012 'default' kwarg (which itself defaults to None)."""
1013 if self.module is None or not hasattr(self.module, function_name):
1014 return kwargs.get("default", None)
1015 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1016
1017 def FullOTA_Assertions(self):
1018 """Called after emitting the block of assertions at the top of a
1019 full OTA package. Implementations can add whatever additional
1020 assertions they like."""
1021 return self._DoCall("FullOTA_Assertions")
1022
Doug Zongkere5ff5902012-01-17 10:55:37 -08001023 def FullOTA_InstallBegin(self):
1024 """Called at the start of full OTA installation."""
1025 return self._DoCall("FullOTA_InstallBegin")
1026
Doug Zongker05d3dea2009-06-22 11:32:31 -07001027 def FullOTA_InstallEnd(self):
1028 """Called at the end of full OTA installation; typically this is
1029 used to install the image for the device's baseband processor."""
1030 return self._DoCall("FullOTA_InstallEnd")
1031
M1chad27ccf72014-11-25 15:30:48 +01001032 def FullOTA_PostValidate(self):
1033 """Called after installing and validating /system; typically this is
1034 used to resize the system partition after a block based installation."""
1035 return self._DoCall("FullOTA_PostValidate")
1036
Doug Zongker05d3dea2009-06-22 11:32:31 -07001037 def IncrementalOTA_Assertions(self):
1038 """Called after emitting the block of assertions at the top of an
1039 incremental OTA package. Implementations can add whatever
1040 additional assertions they like."""
1041 return self._DoCall("IncrementalOTA_Assertions")
1042
Doug Zongkere5ff5902012-01-17 10:55:37 -08001043 def IncrementalOTA_VerifyBegin(self):
1044 """Called at the start of the verification phase of incremental
1045 OTA installation; additional checks can be placed here to abort
1046 the script before any changes are made."""
1047 return self._DoCall("IncrementalOTA_VerifyBegin")
1048
Doug Zongker05d3dea2009-06-22 11:32:31 -07001049 def IncrementalOTA_VerifyEnd(self):
1050 """Called at the end of the verification phase of incremental OTA
1051 installation; additional checks can be placed here to abort the
1052 script before any changes are made."""
1053 return self._DoCall("IncrementalOTA_VerifyEnd")
1054
Doug Zongkere5ff5902012-01-17 10:55:37 -08001055 def IncrementalOTA_InstallBegin(self):
1056 """Called at the start of incremental OTA installation (after
1057 verification is complete)."""
1058 return self._DoCall("IncrementalOTA_InstallBegin")
1059
Doug Zongker05d3dea2009-06-22 11:32:31 -07001060 def IncrementalOTA_InstallEnd(self):
1061 """Called at the end of incremental OTA installation; typically
1062 this is used to install the image for the device's baseband
1063 processor."""
1064 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001065
1066class File(object):
1067 def __init__(self, name, data):
1068 self.name = name
1069 self.data = data
1070 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001071 self.sha1 = sha1(data).hexdigest()
1072
1073 @classmethod
1074 def FromLocalFile(cls, name, diskname):
1075 f = open(diskname, "rb")
1076 data = f.read()
1077 f.close()
1078 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001079
1080 def WriteToTemp(self):
1081 t = tempfile.NamedTemporaryFile()
1082 t.write(self.data)
1083 t.flush()
1084 return t
1085
Geremy Condra36bd3652014-02-06 19:45:10 -08001086 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001087 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001088
1089DIFF_PROGRAM_BY_EXT = {
1090 ".gz" : "imgdiff",
1091 ".zip" : ["imgdiff", "-z"],
1092 ".jar" : ["imgdiff", "-z"],
1093 ".apk" : ["imgdiff", "-z"],
1094 ".img" : "imgdiff",
1095 }
1096
1097class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001098 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001099 self.tf = tf
1100 self.sf = sf
1101 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001102 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001103
1104 def ComputePatch(self):
1105 """Compute the patch (as a string of data) needed to turn sf into
1106 tf. Returns the same tuple as GetPatch()."""
1107
1108 tf = self.tf
1109 sf = self.sf
1110
Doug Zongker24cd2802012-08-14 16:36:15 -07001111 if self.diff_program:
1112 diff_program = self.diff_program
1113 else:
1114 ext = os.path.splitext(tf.name)[1]
1115 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001116
1117 ttemp = tf.WriteToTemp()
1118 stemp = sf.WriteToTemp()
1119
1120 ext = os.path.splitext(tf.name)[1]
1121
1122 try:
1123 ptemp = tempfile.NamedTemporaryFile()
1124 if isinstance(diff_program, list):
1125 cmd = copy.copy(diff_program)
1126 else:
1127 cmd = [diff_program]
1128 cmd.append(stemp.name)
1129 cmd.append(ttemp.name)
1130 cmd.append(ptemp.name)
1131 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001132 err = []
1133 def run():
1134 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001135 if e:
1136 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001137 th = threading.Thread(target=run)
1138 th.start()
1139 th.join(timeout=300) # 5 mins
1140 if th.is_alive():
1141 print "WARNING: diff command timed out"
1142 p.terminate()
1143 th.join(5)
1144 if th.is_alive():
1145 p.kill()
1146 th.join()
1147
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001148 if err or p.returncode != 0:
Doug Zongkerf8340082014-08-05 10:39:37 -07001149 print "WARNING: failure running %s:\n%s\n" % (
1150 diff_program, "".join(err))
1151 self.patch = None
1152 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001153 diff = ptemp.read()
1154 finally:
1155 ptemp.close()
1156 stemp.close()
1157 ttemp.close()
1158
1159 self.patch = diff
1160 return self.tf, self.sf, self.patch
1161
1162
1163 def GetPatch(self):
1164 """Return a tuple (target_file, source_file, patch_data).
1165 patch_data may be None if ComputePatch hasn't been called, or if
1166 computing the patch failed."""
1167 return self.tf, self.sf, self.patch
1168
1169
1170def ComputeDifferences(diffs):
1171 """Call ComputePatch on all the Difference objects in 'diffs'."""
1172 print len(diffs), "diffs to compute"
1173
1174 # Do the largest files first, to try and reduce the long-pole effect.
1175 by_size = [(i.tf.size, i) for i in diffs]
1176 by_size.sort(reverse=True)
1177 by_size = [i[1] for i in by_size]
1178
1179 lock = threading.Lock()
1180 diff_iter = iter(by_size) # accessed under lock
1181
1182 def worker():
1183 try:
1184 lock.acquire()
1185 for d in diff_iter:
1186 lock.release()
1187 start = time.time()
1188 d.ComputePatch()
1189 dur = time.time() - start
1190 lock.acquire()
1191
1192 tf, sf, patch = d.GetPatch()
1193 if sf.name == tf.name:
1194 name = tf.name
1195 else:
1196 name = "%s (%s)" % (tf.name, sf.name)
1197 if patch is None:
1198 print "patching failed! %s" % (name,)
1199 else:
1200 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1201 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
1202 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001203 except Exception as e:
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001204 print e
1205 raise
1206
1207 # start worker threads; wait for them all to finish.
1208 threads = [threading.Thread(target=worker)
1209 for i in range(OPTIONS.worker_threads)]
1210 for th in threads:
1211 th.start()
1212 while threads:
1213 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001214
1215
Dan Albert8b72aef2015-03-23 19:13:21 -07001216class BlockDifference(object):
1217 def __init__(self, partition, tgt, src=None, check_first_block=False,
1218 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001219 self.tgt = tgt
1220 self.src = src
1221 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001222 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001223
Tao Bao5ece99d2015-05-12 11:42:31 -07001224 # Due to http://b/20939131, check_first_block is disabled temporarily.
1225 assert not self.check_first_block
1226
Tao Baodd2a5892015-03-12 12:32:37 -07001227 if version is None:
1228 version = 1
1229 if OPTIONS.info_dict:
1230 version = max(
1231 int(i) for i in
1232 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1233 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001234
1235 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001236 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001237 tmpdir = tempfile.mkdtemp()
1238 OPTIONS.tempfiles.append(tmpdir)
1239 self.path = os.path.join(tmpdir, partition)
1240 b.Compute(self.path)
1241
Tao Baoe09359a2015-10-13 16:37:12 -07001242 if src is None:
1243 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1244 else:
1245 _, self.device = GetTypeAndDevice("/" + partition,
1246 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001247
1248 def WriteScript(self, script, output_zip, progress=None):
1249 if not self.src:
1250 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001251 script.Print("Patching %s image unconditionally..." % (self.partition,))
1252 else:
1253 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001254
Dan Albert8b72aef2015-03-23 19:13:21 -07001255 if progress:
1256 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001257 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001258 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001259
1260 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001261 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001262 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001263 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001264 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001265 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1266 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001267 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001268 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1269 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001270 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001271 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001272 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001273 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001274 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001275 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001276 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001277 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001278 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001279
Tao Baodd2a5892015-03-12 12:32:37 -07001280 # When generating incrementals for the system and vendor partitions,
1281 # explicitly check the first block (which contains the superblock) of
1282 # the partition to see if it's what we expect. If this check fails,
1283 # give an explicit log message about the partition having been
1284 # remounted R/W (the most likely explanation) and the need to flash to
1285 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001286 if self.check_first_block:
1287 self._CheckFirstBlock(script)
1288
Tao Baodd2a5892015-03-12 12:32:37 -07001289 # Abort the OTA update. Note that the incremental OTA cannot be applied
1290 # even if it may match the checksum of the target partition.
1291 # a) If version < 3, operations like move and erase will make changes
1292 # unconditionally and damage the partition.
1293 # b) If version >= 3, it won't even reach here.
1294 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1295 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001296
Tao Bao5fcaaef2015-06-01 13:40:49 -07001297 def _WritePostInstallVerifyScript(self, script):
1298 partition = self.partition
1299 script.Print('Verifying the updated %s image...' % (partition,))
1300 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1301 ranges = self.tgt.care_map
1302 ranges_str = ranges.to_string_raw()
1303 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1304 self.device, ranges_str,
1305 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001306
1307 # Bug: 20881595
1308 # Verify that extended blocks are really zeroed out.
1309 if self.tgt.extended:
1310 ranges_str = self.tgt.extended.to_string_raw()
1311 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1312 self.device, ranges_str,
1313 self._HashZeroBlocks(self.tgt.extended.size())))
1314 script.Print('Verified the updated %s image.' % (partition,))
1315 script.AppendExtra(
1316 'else\n'
1317 ' abort("%s partition has unexpected non-zero contents after OTA '
1318 'update");\n'
1319 'endif;' % (partition,))
1320 else:
1321 script.Print('Verified the updated %s image.' % (partition,))
1322
Tao Bao5fcaaef2015-06-01 13:40:49 -07001323 script.AppendExtra(
1324 'else\n'
1325 ' abort("%s partition has unexpected contents after OTA update");\n'
1326 'endif;' % (partition,))
1327
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001328 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001329 ZipWrite(output_zip,
1330 '{}.transfer.list'.format(self.path),
1331 '{}.transfer.list'.format(self.partition))
1332 ZipWrite(output_zip,
1333 '{}.new.dat'.format(self.path),
1334 '{}.new.dat'.format(self.partition))
1335 ZipWrite(output_zip,
1336 '{}.patch.dat'.format(self.path),
1337 '{}.patch.dat'.format(self.partition),
1338 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001339
Dan Albert8e0178d2015-01-27 15:53:15 -08001340 call = ('block_image_update("{device}", '
1341 'package_extract_file("{partition}.transfer.list"), '
1342 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1343 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001344 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001345
Dan Albert8b72aef2015-03-23 19:13:21 -07001346 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001347 data = source.ReadRangeSet(ranges)
1348 ctx = sha1()
1349
1350 for p in data:
1351 ctx.update(p)
1352
1353 return ctx.hexdigest()
1354
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001355 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1356 """Return the hash value for all zero blocks."""
1357 zero_block = '\x00' * 4096
1358 ctx = sha1()
1359 for _ in range(num_blocks):
1360 ctx.update(zero_block)
1361
1362 return ctx.hexdigest()
1363
Tao Bao5ece99d2015-05-12 11:42:31 -07001364 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1365 # remounting R/W. Will change the checking to a finer-grained way to
1366 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001367 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001368 r = rangelib.RangeSet((0, 1))
1369 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001370
1371 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1372 'abort("%s has been remounted R/W; '
1373 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001374 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001375 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001376
1377DataImage = blockimgdiff.DataImage
1378
1379
Doug Zongker96a57e72010-09-26 14:57:41 -07001380# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001381PARTITION_TYPES = {
1382 "yaffs2": "MTD",
1383 "mtd": "MTD",
1384 "ext4": "EMMC",
1385 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001386 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001387 "squashfs": "EMMC",
1388 "ext2": "EMMC",
1389 "ext3": "EMMC",
Stephen Bird9f657582015-10-08 02:05:06 -07001390 "vfat": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001391}
Doug Zongker96a57e72010-09-26 14:57:41 -07001392
1393def GetTypeAndDevice(mount_point, info):
1394 fstab = info["fstab"]
1395 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001396 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1397 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001398 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001399 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001400
1401
1402def ParseCertificate(data):
1403 """Parse a PEM-format certificate."""
1404 cert = []
1405 save = False
1406 for line in data.split("\n"):
1407 if "--END CERTIFICATE--" in line:
1408 break
1409 if save:
1410 cert.append(line)
1411 if "--BEGIN CERTIFICATE--" in line:
1412 save = True
1413 cert = "".join(cert).decode('base64')
1414 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001415
Doug Zongker412c02f2014-02-13 10:58:24 -08001416def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1417 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001418 """Generate a binary patch that creates the recovery image starting
1419 with the boot image. (Most of the space in these images is just the
1420 kernel, which is identical for the two, so the resulting patch
1421 should be efficient.) Add it to the output zip, along with a shell
1422 script that is run from init.rc on first boot to actually do the
1423 patching and install the new recovery image.
1424
1425 recovery_img and boot_img should be File objects for the
1426 corresponding images. info should be the dictionary returned by
1427 common.LoadInfoDict() on the input target_files.
1428 """
1429
Doug Zongker412c02f2014-02-13 10:58:24 -08001430 if info_dict is None:
1431 info_dict = OPTIONS.info_dict
1432
Doug Zongkerc9253822014-02-04 12:17:58 -08001433 diff_program = ["imgdiff"]
1434 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1435 if os.path.exists(path):
1436 diff_program.append("-b")
1437 diff_program.append(path)
1438 bonus_args = "-b /system/etc/recovery-resource.dat"
1439 else:
1440 bonus_args = ""
1441
1442 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1443 _, _, patch = d.ComputePatch()
1444 output_sink("recovery-from-boot.p", patch)
1445
Dan Albertebb19aa2015-03-27 19:11:53 -07001446 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001447 # The following GetTypeAndDevice()s need to use the path in the target
1448 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001449 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1450 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1451 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001452 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001453
1454 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001455if [ -f /system/etc/recovery-transform.sh ]; then
1456 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1457fi
1458
Doug Zongkerc9253822014-02-04 12:17:58 -08001459if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1460 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"
1461else
1462 log -t recovery "Recovery image already installed"
1463fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001464""" % {'boot_size': boot_img.size,
1465 'boot_sha1': boot_img.sha1,
1466 'recovery_size': recovery_img.size,
1467 'recovery_sha1': recovery_img.sha1,
1468 'boot_type': boot_type,
1469 'boot_device': boot_device,
1470 'recovery_type': recovery_type,
1471 'recovery_device': recovery_device,
1472 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001473
1474 # The install script location moved from /system/etc to /system/bin
1475 # in the L release. Parse the init.rc file to find out where the
1476 # target-files expects it to be, and put it there.
1477 sh_location = "etc/install-recovery.sh"
1478 try:
1479 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1480 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001481 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001482 if m:
1483 sh_location = m.group(1)
1484 print "putting script in", sh_location
1485 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001486 except (OSError, IOError) as e:
Doug Zongkerc9253822014-02-04 12:17:58 -08001487 print "failed to read init.rc: %s" % (e,)
1488
1489 output_sink(sh_location, sh)