blob: de2660a81048fa860532191e2eaabbf4a4c074c8 [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
Anthony Kingc713d762015-11-03 00:23:11 +000015from __future__ import print_function
16
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070021import imp
Doug Zongkereef39442009-04-02 12:14:19 -070022import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080023import platform
Doug Zongkereef39442009-04-02 12:14:19 -070024import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070025import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070026import shutil
27import subprocess
28import sys
29import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070030import threading
31import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070032import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070033
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070034import blockimgdiff
Dan Albert8b72aef2015-03-23 19:13:21 -070035import rangelib
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070036
Tao Baof3282b42015-04-01 11:21:55 -070037from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080038
Anthony Kingc713d762015-11-03 00:23:11 +000039try:
40 raw_input
41except NameError:
42 raw_input = input
43
44
45def iteritems(obj):
46 if hasattr(obj, 'iteritems'):
47 return obj.iteritems()
48 return obj.items()
49
Doug Zongkereef39442009-04-02 12:14:19 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
52 def __init__(self):
53 platform_search_path = {
54 "linux2": "out/host/linux-x86",
55 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070056 }
Doug Zongker85448772014-09-09 14:59:20 -070057
Dan Albert8b72aef2015-03-23 19:13:21 -070058 self.search_path = platform_search_path.get(sys.platform, None)
59 self.signapk_path = "framework/signapk.jar" # Relative to search_path
60 self.extra_signapk_args = []
61 self.java_path = "java" # Use the one on the path by default.
62 self.java_args = "-Xmx2048m" # JVM Args
63 self.public_key_suffix = ".x509.pem"
64 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070065 # use otatools built boot_signer by default
66 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070067 self.boot_signer_args = []
68 self.verity_signer_path = None
69 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.verbose = False
71 self.tempfiles = []
72 self.device_specific = None
73 self.extras = {}
74 self.info_dict = None
Tao Baoe09359a2015-10-13 16:37:12 -070075 self.source_info_dict = None
76 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.worker_threads = None
78
79
80OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070081
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080082
83# Values for "certificate" in apkcerts that mean special things.
84SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
85
Tao Baoe9647142015-08-07 19:49:45 -070086# Stash size cannot exceed cache_size * threshold.
87OPTIONS.cache_size = None
88OPTIONS.stash_threshold = 0.8
89
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080090
Dan Albert8b72aef2015-03-23 19:13:21 -070091class ExternalError(RuntimeError):
92 pass
Doug Zongkereef39442009-04-02 12:14:19 -070093
94
95def Run(args, **kwargs):
96 """Create and return a subprocess.Popen object, printing the command
97 line on the terminal if -v was specified."""
98 if OPTIONS.verbose:
Anthony Kingc713d762015-11-03 00:23:11 +000099 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700100 return subprocess.Popen(args, **kwargs)
101
102
Ying Wang7e6d4e42010-12-13 16:25:36 -0800103def CloseInheritedPipes():
104 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
105 before doing other work."""
106 if platform.system() != "Darwin":
107 return
108 for d in range(3, 1025):
109 try:
110 stat = os.fstat(d)
111 if stat is not None:
112 pipebit = stat[0] & 0x1000
113 if pipebit != 0:
114 os.close(d)
115 except OSError:
116 pass
117
118
Dan Albert8b72aef2015-03-23 19:13:21 -0700119def LoadInfoDict(input_file):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700120 """Read and parse the META/misc_info.txt key/value pairs from the
121 input target files and return a dict."""
122
Doug Zongkerc9253822014-02-04 12:17:58 -0800123 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700124 if isinstance(input_file, zipfile.ZipFile):
125 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800126 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700127 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800128 try:
129 with open(path) as f:
130 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700131 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800132 if e.errno == errno.ENOENT:
133 raise KeyError(fn)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700134 d = {}
135 try:
Michael Runge6e836112014-04-15 17:40:21 -0700136 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700137 except KeyError:
138 # ok if misc_info.txt doesn't exist
139 pass
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700140
Doug Zongker37974732010-09-16 17:44:38 -0700141 # backwards compatibility: These values used to be in their own
142 # files. Look for them, in case we're processing an old
143 # target_files zip.
144
145 if "mkyaffs2_extra_flags" not in d:
146 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700147 d["mkyaffs2_extra_flags"] = read_helper(
148 "META/mkyaffs2-extra-flags.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700149 except KeyError:
150 # ok if flags don't exist
151 pass
152
153 if "recovery_api_version" not in d:
154 try:
Dan Albert8b72aef2015-03-23 19:13:21 -0700155 d["recovery_api_version"] = read_helper(
156 "META/recovery-api-version.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700157 except KeyError:
158 raise ValueError("can't find recovery API version in input target-files")
159
160 if "tool_extensions" not in d:
161 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800162 d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
Doug Zongker37974732010-09-16 17:44:38 -0700163 except KeyError:
164 # ok if extensions don't exist
165 pass
166
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800167 if "fstab_version" not in d:
168 d["fstab_version"] = "1"
169
Ameya Thakure5b5a272013-07-29 17:39:37 -0700170 if "device_type" not in d:
171 d["device_type"] = "MMC"
Doug Zongker37974732010-09-16 17:44:38 -0700172 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800173 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700174 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700175 if not line:
176 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700177 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700178 if not value:
179 continue
Doug Zongker37974732010-09-16 17:44:38 -0700180 if name == "blocksize":
181 d[name] = value
182 else:
183 d[name + "_size"] = value
184 except KeyError:
185 pass
186
187 def makeint(key):
188 if key in d:
189 d[key] = int(d[key], 0)
190
191 makeint("recovery_api_version")
192 makeint("blocksize")
193 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700194 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700195 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700196 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700197 makeint("recovery_size")
198 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800199 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700200
Ameya Thakure5b5a272013-07-29 17:39:37 -0700201 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d["device_type"])
Doug Zongkerc9253822014-02-04 12:17:58 -0800202 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700203 return d
204
Doug Zongkerc9253822014-02-04 12:17:58 -0800205def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700206 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800207 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700208 except KeyError:
Anthony Kingc713d762015-11-03 00:23:11 +0000209 print("Warning: could not find SYSTEM/build.prop in %s" % zip)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700210 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700211 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700212
Michael Runge6e836112014-04-15 17:40:21 -0700213def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700214 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700215 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700216 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700217 if not line or line.startswith("#"):
218 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700219 if "=" in line:
220 name, value = line.split("=", 1)
221 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700222 return d
223
Ameya Thakure5b5a272013-07-29 17:39:37 -0700224def LoadRecoveryFSTab(read_helper, fstab_version, type):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700225 class Partition(object):
Tao Bao548eb762015-06-10 12:32:41 -0700226 def __init__(self, mount_point, fs_type, device, length, device2, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700227 self.mount_point = mount_point
228 self.fs_type = fs_type
229 self.device = device
230 self.length = length
231 self.device2 = device2
Tao Bao548eb762015-06-10 12:32:41 -0700232 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700233
234 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800235 data = read_helper("RECOVERY/RAMDISK/etc/recovery.fstab")
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700236 except KeyError:
Anthony Kingc713d762015-11-03 00:23:11 +0000237 print("Warning: could not find RECOVERY/RAMDISK/etc/recovery.fstab")
Jeff Davidson033fbe22011-10-26 18:08:09 -0700238 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700239
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800240 if fstab_version == 1:
241 d = {}
242 for line in data.split("\n"):
243 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700244 if not line or line.startswith("#"):
245 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800246 pieces = line.split()
Dan Albert8b72aef2015-03-23 19:13:21 -0700247 if not 3 <= len(pieces) <= 4:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800248 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800249 options = None
250 if len(pieces) >= 4:
251 if pieces[3].startswith("/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700252 device2 = pieces[3]
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800253 if len(pieces) >= 5:
254 options = pieces[4]
255 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700256 device2 = None
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800257 options = pieces[3]
Doug Zongker086cbb02011-02-17 15:54:20 -0800258 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700259 device2 = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700260
Dan Albert8b72aef2015-03-23 19:13:21 -0700261 mount_point = pieces[0]
262 length = 0
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800263 if options:
264 options = options.split(",")
265 for i in options:
266 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700267 length = int(i[7:])
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800268 else:
Anthony Kingc713d762015-11-03 00:23:11 +0000269 print("%s: unknown option \"%s\"" % (mount_point, i))
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800270
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[1],
272 device=pieces[2], length=length,
273 device2=device2)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800274
275 elif fstab_version == 2:
276 d = {}
277 for line in data.split("\n"):
278 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 if not line or line.startswith("#"):
280 continue
Tao Bao548eb762015-06-10 12:32:41 -0700281 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800282 pieces = line.split()
283 if len(pieces) != 5:
284 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
285
286 # Ignore entries that are managed by vold
287 options = pieces[4]
Dan Albert8b72aef2015-03-23 19:13:21 -0700288 if "voldmanaged=" in options:
289 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800290
291 # It's a good line, parse it
Dan Albert8b72aef2015-03-23 19:13:21 -0700292 length = 0
Doug Zongker086cbb02011-02-17 15:54:20 -0800293 options = options.split(",")
294 for i in options:
295 if i.startswith("length="):
Dan Albert8b72aef2015-03-23 19:13:21 -0700296 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800297 else:
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800298 # Ignore all unknown options in the unified fstab
299 continue
Doug Zongker086cbb02011-02-17 15:54:20 -0800300
Tao Bao548eb762015-06-10 12:32:41 -0700301 mount_flags = pieces[3]
302 # Honor the SELinux context if present.
303 context = None
304 for i in mount_flags.split(","):
305 if i.startswith("context="):
306 context = i
307
Dan Albert8b72aef2015-03-23 19:13:21 -0700308 mount_point = pieces[1]
309 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Tao Bao548eb762015-06-10 12:32:41 -0700310 device=pieces[0], length=length,
311 device2=None, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800312
313 else:
314 raise ValueError("Unknown fstab_version: \"%d\"" % (fstab_version,))
315
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700316 return d
317
318
Doug Zongker37974732010-09-16 17:44:38 -0700319def DumpInfoDict(d):
320 for k, v in sorted(d.items()):
Anthony Kingc713d762015-11-03 00:23:11 +0000321 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700322
Dan Albert8b72aef2015-03-23 19:13:21 -0700323
Doug Zongkerd5131602012-08-02 14:46:42 -0700324def BuildBootableImage(sourcedir, fs_config_file, info_dict=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700325 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700326 'sourcedir'), and turn them into a boot image. Return the image
327 data, or None if sourcedir does not appear to contains files for
328 building the requested image."""
329
330 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
331 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
332 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700333
Doug Zongkerd5131602012-08-02 14:46:42 -0700334 if info_dict is None:
335 info_dict = OPTIONS.info_dict
336
Doug Zongkereef39442009-04-02 12:14:19 -0700337 ramdisk_img = tempfile.NamedTemporaryFile()
338 img = tempfile.NamedTemporaryFile()
339
Doug Zongkerfffe1d52012-05-03 16:15:29 -0700340 if os.access(fs_config_file, os.F_OK):
341 cmd = ["mkbootfs", "-f", fs_config_file, os.path.join(sourcedir, "RAMDISK")]
342 else:
343 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
344 p1 = Run(cmd, stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700345 p2 = Run(["minigzip"],
346 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700347
348 p2.wait()
349 p1.wait()
Dan Albert8b72aef2015-03-23 19:13:21 -0700350 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
351 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
Doug Zongkereef39442009-04-02 12:14:19 -0700352
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000353 """check if uboot is requested"""
354 fn = os.path.join(sourcedir, "ubootargs")
Benoit Fradina45a8682014-07-14 21:00:43 +0200355 if os.access(fn, os.F_OK):
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000356 cmd = ["mkimage"]
357 for argument in open(fn).read().rstrip("\n").split(" "):
358 cmd.append(argument)
359 cmd.append("-d")
360 cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name)
361 cmd.append(img.name)
Benoit Fradina45a8682014-07-14 21:00:43 +0200362
Tao Baod95e9fd2015-03-29 23:07:41 -0700363 else:
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000364 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
365 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
366 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700367
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000368 fn = os.path.join(sourcedir, "second")
369 if os.access(fn, os.F_OK):
370 cmd.append("--second")
371 cmd.append(fn)
372
373 fn = os.path.join(sourcedir, "cmdline")
374 if os.access(fn, os.F_OK):
375 cmd.append("--cmdline")
376 cmd.append(open(fn).read().rstrip("\n"))
377
378 fn = os.path.join(sourcedir, "base")
379 if os.access(fn, os.F_OK):
380 cmd.append("--base")
381 cmd.append(open(fn).read().rstrip("\n"))
382
383 fn = os.path.join(sourcedir, "tagsaddr")
384 if os.access(fn, os.F_OK):
385 cmd.append("--tags-addr")
386 cmd.append(open(fn).read().rstrip("\n"))
387
Ameya Thakure5b5a272013-07-29 17:39:37 -0700388 fn = os.path.join(sourcedir, "tags_offset")
389 if os.access(fn, os.F_OK):
390 cmd.append("--tags_offset")
391 cmd.append(open(fn).read().rstrip("\n"))
392
Ricardo Cerqueiraa4333b12011-11-17 00:13:29 +0000393 fn = os.path.join(sourcedir, "ramdisk_offset")
394 if os.access(fn, os.F_OK):
395 cmd.append("--ramdisk_offset")
396 cmd.append(open(fn).read().rstrip("\n"))
397
398 fn = os.path.join(sourcedir, "dt_args")
399 if os.access(fn, os.F_OK):
400 cmd.append("--dt")
401 cmd.append(open(fn).read().rstrip("\n"))
402
403 fn = os.path.join(sourcedir, "pagesize")
404 if os.access(fn, os.F_OK):
405 cmd.append("--pagesize")
406 cmd.append(open(fn).read().rstrip("\n"))
407
408 args = info_dict.get("mkbootimg_args", None)
409 if args and args.strip():
410 cmd.extend(shlex.split(args))
411
412 img_unsigned = None
413 if info_dict.get("vboot", None):
414 img_unsigned = tempfile.NamedTemporaryFile()
415 cmd.extend(["--ramdisk", ramdisk_img.name,
416 "--output", img_unsigned.name])
417 else:
418 cmd.extend(["--ramdisk", ramdisk_img.name,
419 "--output", img.name])
420
Doug Zongker38a649f2009-06-17 09:07:09 -0700421 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700422 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700423 assert p.returncode == 0, "mkbootimg of %s image failed" % (
424 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700425
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100426 if (info_dict.get("boot_signer", None) == "true" and
427 info_dict.get("verity_key", None)):
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700428 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700429 cmd = [OPTIONS.boot_signer_path]
430 cmd.extend(OPTIONS.boot_signer_args)
431 cmd.extend([path, img.name,
432 info_dict["verity_key"] + ".pk8",
433 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700434 p = Run(cmd, stdout=subprocess.PIPE)
435 p.communicate()
436 assert p.returncode == 0, "boot_signer of %s image failed" % path
437
Tao Baod95e9fd2015-03-29 23:07:41 -0700438 # Sign the image if vboot is non-empty.
439 elif info_dict.get("vboot", None):
440 path = "/" + os.path.basename(sourcedir).lower()
441 img_keyblock = tempfile.NamedTemporaryFile()
442 cmd = [info_dict["vboot_signer_cmd"], info_dict["futility"],
443 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700444 info_dict["vboot_key"] + ".vbprivk",
445 info_dict["vboot_subkey"] + ".vbprivk",
446 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700447 img.name]
448 p = Run(cmd, stdout=subprocess.PIPE)
449 p.communicate()
450 assert p.returncode == 0, "vboot_signer of %s image failed" % path
451
Tao Baof3282b42015-04-01 11:21:55 -0700452 # Clean up the temp files.
453 img_unsigned.close()
454 img_keyblock.close()
455
Doug Zongkereef39442009-04-02 12:14:19 -0700456 img.seek(os.SEEK_SET, 0)
457 data = img.read()
458
459 ramdisk_img.close()
460 img.close()
461
462 return data
463
464
Doug Zongkerd5131602012-08-02 14:46:42 -0700465def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
466 info_dict=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800467 """Return a File object (with name 'name') with the desired bootable
468 image. Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
Doug Zongker6f1d0312014-08-22 08:07:12 -0700469 'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
470 otherwise construct it from the source files in
Doug Zongker55d93282011-01-25 17:03:34 -0800471 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700472
Doug Zongker55d93282011-01-25 17:03:34 -0800473 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
474 if os.path.exists(prebuilt_path):
Anthony Kingc713d762015-11-03 00:23:11 +0000475 print("using prebuilt %s from BOOTABLE_IMAGES..." % prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800476 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700477
478 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
479 if os.path.exists(prebuilt_path):
Anthony Kingc713d762015-11-03 00:23:11 +0000480 print("using prebuilt %s from IMAGES..." % prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700481 return File.FromLocalFile(name, prebuilt_path)
482
Anthony Kingc713d762015-11-03 00:23:11 +0000483 print("building image from target_files %s..." % tree_subdir)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700484 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
485 data = BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
486 os.path.join(unpack_dir, fs_config),
487 info_dict)
488 if data:
489 return File(name, data)
490 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800491
Doug Zongkereef39442009-04-02 12:14:19 -0700492
Doug Zongker75f17362009-12-08 13:46:44 -0800493def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800494 """Unzip the given archive into a temporary directory and return the name.
495
496 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
497 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
498
499 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
500 main file), open for reading.
501 """
Doug Zongkereef39442009-04-02 12:14:19 -0700502
503 tmp = tempfile.mkdtemp(prefix="targetfiles-")
504 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800505
506 def unzip_to_dir(filename, dirname):
Dan Pasanen3d4039a2015-02-07 18:59:36 -0600507 subprocess.call(["rm", "-rf", dirname + filename, "targetfiles-*"])
Doug Zongker55d93282011-01-25 17:03:34 -0800508 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
509 if pattern is not None:
510 cmd.append(pattern)
511 p = Run(cmd, stdout=subprocess.PIPE)
512 p.communicate()
513 if p.returncode != 0:
514 raise ExternalError("failed to unzip input target-files \"%s\"" %
515 (filename,))
516
517 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
518 if m:
519 unzip_to_dir(m.group(1), tmp)
520 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
521 filename = m.group(1)
522 else:
523 unzip_to_dir(filename, tmp)
524
525 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700526
527
528def GetKeyPasswords(keylist):
529 """Given a list of keys, prompt the user to enter passwords for
530 those which require them. Return a {key: password} dict. password
531 will be None if the key has no password."""
532
Doug Zongker8ce7c252009-05-22 13:34:54 -0700533 no_passwords = []
534 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700535 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700536 devnull = open("/dev/null", "w+b")
537 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800538 # We don't need a password for things that aren't really keys.
539 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700540 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700541 continue
542
T.R. Fullhart37e10522013-03-18 10:31:26 -0700543 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700544 "-inform", "DER", "-nocrypt"],
545 stdin=devnull.fileno(),
546 stdout=devnull.fileno(),
547 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700548 p.communicate()
549 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700550 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700551 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700552 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700553 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
554 "-inform", "DER", "-passin", "pass:"],
555 stdin=devnull.fileno(),
556 stdout=devnull.fileno(),
557 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700558 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700559 if p.returncode == 0:
560 # Encrypted key with empty string as password.
561 key_passwords[k] = ''
Anthony Kingc713d762015-11-03 00:23:11 +0000562 elif stderr.startswith(b'Error decrypting key'):
T.R. Fullhart37e10522013-03-18 10:31:26 -0700563 # Definitely encrypted key.
564 # It would have said "Error reading key" if it didn't parse correctly.
565 need_passwords.append(k)
566 else:
567 # Potentially, a type of key that openssl doesn't understand.
568 # We'll let the routines in signapk.jar handle it.
569 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700570 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700571
T.R. Fullhart37e10522013-03-18 10:31:26 -0700572 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700573 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700574 return key_passwords
575
576
Doug Zongker951495f2009-08-14 12:44:19 -0700577def SignFile(input_name, output_name, key, password, align=None,
578 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700579 """Sign the input_name zip/jar/apk, producing output_name. Use the
580 given key and password (the latter may be None if the key does not
581 have a password.
582
583 If align is an integer > 1, zipalign is run to align stored files in
584 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700585
586 If whole_file is true, use the "-w" option to SignApk to embed a
587 signature that covers the whole file in the archive comment of the
588 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700589 """
Doug Zongker951495f2009-08-14 12:44:19 -0700590
Doug Zongkereef39442009-04-02 12:14:19 -0700591 if align == 0 or align == 1:
592 align = None
593
594 if align:
595 temp = tempfile.NamedTemporaryFile()
596 sign_name = temp.name
597 else:
598 sign_name = output_name
599
Baligh Uddin339ee492014-09-05 11:18:07 -0700600 cmd = [OPTIONS.java_path, OPTIONS.java_args, "-jar",
T.R. Fullhart37e10522013-03-18 10:31:26 -0700601 os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)]
602 cmd.extend(OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700603 if whole_file:
604 cmd.append("-w")
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 cmd.extend([key + OPTIONS.public_key_suffix,
606 key + OPTIONS.private_key_suffix,
Doug Zongker951495f2009-08-14 12:44:19 -0700607 input_name, sign_name])
608
609 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700610 if password is not None:
611 password += "\n"
612 p.communicate(password)
613 if p.returncode != 0:
614 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
615
616 if align:
Brian Carlstrom903186f2015-05-22 15:51:19 -0700617 p = Run(["zipalign", "-f", "-p", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700618 p.communicate()
619 if p.returncode != 0:
620 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
621 temp.close()
622
623
Doug Zongker37974732010-09-16 17:44:38 -0700624def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700625 """Check the data string passed against the max size limit, if
626 any, for the given target. Raise exception if the data is too big.
627 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700628
Dan Albert8b72aef2015-03-23 19:13:21 -0700629 if target.endswith(".img"):
630 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700631 mount_point = "/" + target
632
Ying Wangf8824af2014-06-03 14:07:27 -0700633 fs_type = None
634 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700635 if info_dict["fstab"]:
Ethan Chenccc711c2014-06-02 16:49:59 -0700636 if mount_point == "/userdata_extra": mount_point = "/data"
637 if mount_point == "/userdata": mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700638 p = info_dict["fstab"][mount_point]
639 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800640 device = p.device
641 if "/" in device:
642 device = device[device.rfind("/")+1:]
643 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700644 if not fs_type or not limit:
645 return
Doug Zongkereef39442009-04-02 12:14:19 -0700646
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700647 if fs_type == "yaffs2":
648 # image size should be increased by 1/64th to account for the
649 # spare area (64 bytes per 2k page)
650 limit = limit / 2048 * (2048+64)
Andrew Boie0f9aec82012-02-14 09:32:52 -0800651 size = len(data)
652 pct = float(size) * 100.0 / limit
653 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
654 if pct >= 99.0:
655 raise ExternalError(msg)
656 elif pct >= 95.0:
Anthony Kingc713d762015-11-03 00:23:11 +0000657 print()
658 print(" WARNING: ", msg)
659 print()
Andrew Boie0f9aec82012-02-14 09:32:52 -0800660 elif OPTIONS.verbose:
Anthony Kingc713d762015-11-03 00:23:11 +0000661 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700662
663
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800664def ReadApkCerts(tf_zip):
665 """Given a target_files ZipFile, parse the META/apkcerts.txt file
666 and return a {package: cert} dict."""
667 certmap = {}
668 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
669 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700670 if not line:
671 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800672 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
673 r'private_key="(.*)"$', line)
674 if m:
675 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700676 public_key_suffix_len = len(OPTIONS.public_key_suffix)
677 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800678 if cert in SPECIAL_CERT_STRINGS and not privkey:
679 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700680 elif (cert.endswith(OPTIONS.public_key_suffix) and
681 privkey.endswith(OPTIONS.private_key_suffix) and
682 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
683 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800684 else:
685 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
686 return certmap
687
688
Doug Zongkereef39442009-04-02 12:14:19 -0700689COMMON_DOCSTRING = """
690 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700691 Prepend <dir>/bin to the list of places to search for binaries
692 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700693
Doug Zongker05d3dea2009-06-22 11:32:31 -0700694 -s (--device_specific) <file>
695 Path to the python module containing device-specific
696 releasetools code.
697
Doug Zongker8bec09e2009-11-30 15:37:14 -0800698 -x (--extra) <key=value>
699 Add a key/value pair to the 'extras' dict, which device-specific
700 extension code may look at.
701
Doug Zongkereef39442009-04-02 12:14:19 -0700702 -v (--verbose)
703 Show command lines being executed.
704
705 -h (--help)
706 Display this usage message and exit.
707"""
708
709def Usage(docstring):
Anthony Kingc713d762015-11-03 00:23:11 +0000710 print(docstring.rstrip("\n"))
711 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700712
713
714def ParseOptions(argv,
715 docstring,
716 extra_opts="", extra_long_opts=(),
717 extra_option_handler=None):
718 """Parse the options in argv and return any arguments that aren't
719 flags. docstring is the calling module's docstring, to be displayed
720 for errors and -h. extra_opts and extra_long_opts are for flags
721 defined by the caller, which are processed by passing them to
722 extra_option_handler."""
723
724 try:
725 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800726 argv, "hvp:s:x:" + extra_opts,
T.R. Fullhart37e10522013-03-18 10:31:26 -0700727 ["help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700728 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700729 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
730 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800731 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700732 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700733 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700734 Usage(docstring)
Anthony Kingc713d762015-11-03 00:23:11 +0000735 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700736 sys.exit(2)
737
Doug Zongkereef39442009-04-02 12:14:19 -0700738 for o, a in opts:
739 if o in ("-h", "--help"):
740 Usage(docstring)
741 sys.exit()
742 elif o in ("-v", "--verbose"):
743 OPTIONS.verbose = True
744 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700745 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700746 elif o in ("--signapk_path",):
747 OPTIONS.signapk_path = a
748 elif o in ("--extra_signapk_args",):
749 OPTIONS.extra_signapk_args = shlex.split(a)
750 elif o in ("--java_path",):
751 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700752 elif o in ("--java_args",):
753 OPTIONS.java_args = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700754 elif o in ("--public_key_suffix",):
755 OPTIONS.public_key_suffix = a
756 elif o in ("--private_key_suffix",):
757 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800758 elif o in ("--boot_signer_path",):
759 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700760 elif o in ("--boot_signer_args",):
761 OPTIONS.boot_signer_args = shlex.split(a)
762 elif o in ("--verity_signer_path",):
763 OPTIONS.verity_signer_path = a
764 elif o in ("--verity_signer_args",):
765 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700766 elif o in ("-s", "--device_specific"):
767 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800768 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800769 key, value = a.split("=", 1)
770 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700771 else:
772 if extra_option_handler is None or not extra_option_handler(o, a):
773 assert False, "unknown option \"%s\"" % (o,)
774
Doug Zongker85448772014-09-09 14:59:20 -0700775 if OPTIONS.search_path:
776 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
777 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700778
779 return args
780
781
Doug Zongkerfc44a512014-08-26 13:10:25 -0700782def MakeTempFile(prefix=None, suffix=None):
783 """Make a temp file and add it to the list of things to be deleted
784 when Cleanup() is called. Return the filename."""
785 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
786 os.close(fd)
787 OPTIONS.tempfiles.append(fn)
788 return fn
789
790
Doug Zongkereef39442009-04-02 12:14:19 -0700791def Cleanup():
792 for i in OPTIONS.tempfiles:
793 if os.path.isdir(i):
794 shutil.rmtree(i)
795 else:
796 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700797
798
799class PasswordManager(object):
800 def __init__(self):
801 self.editor = os.getenv("EDITOR", None)
802 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
803
804 def GetPasswords(self, items):
805 """Get passwords corresponding to each string in 'items',
806 returning a dict. (The dict may have keys in addition to the
807 values in 'items'.)
808
809 Uses the passwords in $ANDROID_PW_FILE if available, letting the
810 user edit that file to add more needed passwords. If no editor is
811 available, or $ANDROID_PW_FILE isn't define, prompts the user
812 interactively in the ordinary way.
813 """
814
815 current = self.ReadFile()
816
817 first = True
818 while True:
819 missing = []
820 for i in items:
821 if i not in current or not current[i]:
822 missing.append(i)
823 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700824 if not missing:
825 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700826
827 for i in missing:
828 current[i] = ""
829
830 if not first:
Anthony Kingc713d762015-11-03 00:23:11 +0000831 print("key file %s still missing some passwords." % self.pwfile)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700832 answer = raw_input("try to edit again? [y]> ").strip()
833 if answer and answer[0] not in 'yY':
834 raise RuntimeError("key passwords unavailable")
835 first = False
836
837 current = self.UpdateAndReadFile(current)
838
Dan Albert8b72aef2015-03-23 19:13:21 -0700839 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700840 """Prompt the user to enter a value (password) for each key in
841 'current' whose value is fales. Returns a new dict with all the
842 values.
843 """
844 result = {}
Anthony Kingc713d762015-11-03 00:23:11 +0000845 for k, v in sorted(iteritems(current)):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700846 if v:
847 result[k] = v
848 else:
849 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700850 result[k] = getpass.getpass(
851 "Enter password for %s key> " % k).strip()
852 if result[k]:
853 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 return result
855
856 def UpdateAndReadFile(self, current):
857 if not self.editor or not self.pwfile:
858 return self.PromptResult(current)
859
860 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700861 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700862 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
863 f.write("# (Additional spaces are harmless.)\n\n")
864
865 first_line = None
Anthony Kingc713d762015-11-03 00:23:11 +0000866 sorted_list = sorted((not v, k, v) for (k, v) in current.items())
Dan Albert8b72aef2015-03-23 19:13:21 -0700867 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 f.write("[[[ %s ]]] %s\n" % (v, k))
869 if not v and first_line is None:
870 # position cursor on first line with no password.
871 first_line = i + 4
872 f.close()
873
874 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
875 _, _ = p.communicate()
876
877 return self.ReadFile()
878
879 def ReadFile(self):
880 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700881 if self.pwfile is None:
882 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700883 try:
884 f = open(self.pwfile, "r")
885 for line in f:
886 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700887 if not line or line[0] == '#':
888 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700889 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
890 if not m:
Anthony Kingc713d762015-11-03 00:23:11 +0000891 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700892 else:
893 result[m.group(2)] = m.group(1)
894 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700895 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700896 if e.errno != errno.ENOENT:
Anthony Kingc713d762015-11-03 00:23:11 +0000897 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700898 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700899
900
Dan Albert8e0178d2015-01-27 15:53:15 -0800901def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
902 compress_type=None):
903 import datetime
904
905 # http://b/18015246
906 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
907 # for files larger than 2GiB. We can work around this by adjusting their
908 # limit. Note that `zipfile.writestr()` will not work for strings larger than
909 # 2GiB. The Python interpreter sometimes rejects strings that large (though
910 # it isn't clear to me exactly what circumstances cause this).
911 # `zipfile.write()` must be used directly to work around this.
912 #
913 # This mess can be avoided if we port to python3.
914 saved_zip64_limit = zipfile.ZIP64_LIMIT
915 zipfile.ZIP64_LIMIT = (1 << 32) - 1
916
917 if compress_type is None:
918 compress_type = zip_file.compression
919 if arcname is None:
920 arcname = filename
921
922 saved_stat = os.stat(filename)
923
924 try:
925 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
926 # file to be zipped and reset it when we're done.
927 os.chmod(filename, perms)
928
929 # Use a fixed timestamp so the output is repeatable.
930 epoch = datetime.datetime.fromtimestamp(0)
931 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
932 os.utime(filename, (timestamp, timestamp))
933
934 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
935 finally:
936 os.chmod(filename, saved_stat.st_mode)
937 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
938 zipfile.ZIP64_LIMIT = saved_zip64_limit
939
940
Tao Bao58c1b962015-05-20 09:32:18 -0700941def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -0700942 compress_type=None):
943 """Wrap zipfile.writestr() function to work around the zip64 limit.
944
945 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
946 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
947 when calling crc32(bytes).
948
949 But it still works fine to write a shorter string into a large zip file.
950 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
951 when we know the string won't be too long.
952 """
953
954 saved_zip64_limit = zipfile.ZIP64_LIMIT
955 zipfile.ZIP64_LIMIT = (1 << 32) - 1
956
957 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
958 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -0700959 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -0700960 if perms is None:
961 perms = 0o644
Geremy Condra36bd3652014-02-06 19:45:10 -0800962 else:
Tao Baof3282b42015-04-01 11:21:55 -0700963 zinfo = zinfo_or_arcname
964
965 # If compress_type is given, it overrides the value in zinfo.
966 if compress_type is not None:
967 zinfo.compress_type = compress_type
968
Tao Bao58c1b962015-05-20 09:32:18 -0700969 # If perms is given, it has a priority.
970 if perms is not None:
971 zinfo.external_attr = perms << 16
972
Tao Baof3282b42015-04-01 11:21:55 -0700973 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -0700974 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
975
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -0700977 zipfile.ZIP64_LIMIT = saved_zip64_limit
978
979
980def ZipClose(zip_file):
981 # http://b/18015246
982 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
983 # central directory.
984 saved_zip64_limit = zipfile.ZIP64_LIMIT
985 zipfile.ZIP64_LIMIT = (1 << 32) - 1
986
987 zip_file.close()
988
989 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990
991
992class DeviceSpecificParams(object):
993 module = None
994 def __init__(self, **kwargs):
995 """Keyword arguments to the constructor become attributes of this
996 object, which is passed to all functions in the device-specific
997 module."""
Anthony Kingc713d762015-11-03 00:23:11 +0000998 for k, v in iteritems(kwargs):
Doug Zongker05d3dea2009-06-22 11:32:31 -0700999 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001000 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001001
1002 if self.module is None:
1003 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001004 if not path:
1005 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001006 try:
1007 if os.path.isdir(path):
1008 info = imp.find_module("releasetools", [path])
1009 else:
1010 d, f = os.path.split(path)
1011 b, x = os.path.splitext(f)
1012 if x == ".py":
1013 f = b
1014 info = imp.find_module(f, [d])
Anthony Kingc713d762015-11-03 00:23:11 +00001015 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001016 self.module = imp.load_module("device_specific", *info)
1017 except ImportError:
Anthony Kingc713d762015-11-03 00:23:11 +00001018 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001019
1020 def _DoCall(self, function_name, *args, **kwargs):
1021 """Call the named function in the device-specific module, passing
1022 the given args and kwargs. The first argument to the call will be
1023 the DeviceSpecific object itself. If there is no module, or the
1024 module does not define the function, return the value of the
1025 'default' kwarg (which itself defaults to None)."""
1026 if self.module is None or not hasattr(self.module, function_name):
1027 return kwargs.get("default", None)
1028 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1029
1030 def FullOTA_Assertions(self):
1031 """Called after emitting the block of assertions at the top of a
1032 full OTA package. Implementations can add whatever additional
1033 assertions they like."""
1034 return self._DoCall("FullOTA_Assertions")
1035
Doug Zongkere5ff5902012-01-17 10:55:37 -08001036 def FullOTA_InstallBegin(self):
1037 """Called at the start of full OTA installation."""
1038 return self._DoCall("FullOTA_InstallBegin")
1039
Doug Zongker05d3dea2009-06-22 11:32:31 -07001040 def FullOTA_InstallEnd(self):
1041 """Called at the end of full OTA installation; typically this is
1042 used to install the image for the device's baseband processor."""
1043 return self._DoCall("FullOTA_InstallEnd")
1044
M1chad27ccf72014-11-25 15:30:48 +01001045 def FullOTA_PostValidate(self):
1046 """Called after installing and validating /system; typically this is
1047 used to resize the system partition after a block based installation."""
1048 return self._DoCall("FullOTA_PostValidate")
1049
Doug Zongker05d3dea2009-06-22 11:32:31 -07001050 def IncrementalOTA_Assertions(self):
1051 """Called after emitting the block of assertions at the top of an
1052 incremental OTA package. Implementations can add whatever
1053 additional assertions they like."""
1054 return self._DoCall("IncrementalOTA_Assertions")
1055
Doug Zongkere5ff5902012-01-17 10:55:37 -08001056 def IncrementalOTA_VerifyBegin(self):
1057 """Called at the start of the verification phase of incremental
1058 OTA installation; additional checks can be placed here to abort
1059 the script before any changes are made."""
1060 return self._DoCall("IncrementalOTA_VerifyBegin")
1061
Doug Zongker05d3dea2009-06-22 11:32:31 -07001062 def IncrementalOTA_VerifyEnd(self):
1063 """Called at the end of the verification phase of incremental OTA
1064 installation; additional checks can be placed here to abort the
1065 script before any changes are made."""
1066 return self._DoCall("IncrementalOTA_VerifyEnd")
1067
Doug Zongkere5ff5902012-01-17 10:55:37 -08001068 def IncrementalOTA_InstallBegin(self):
1069 """Called at the start of incremental OTA installation (after
1070 verification is complete)."""
1071 return self._DoCall("IncrementalOTA_InstallBegin")
1072
Doug Zongker05d3dea2009-06-22 11:32:31 -07001073 def IncrementalOTA_InstallEnd(self):
1074 """Called at the end of incremental OTA installation; typically
1075 this is used to install the image for the device's baseband
1076 processor."""
1077 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001078
1079class File(object):
1080 def __init__(self, name, data):
1081 self.name = name
1082 self.data = data
1083 self.size = len(data)
Doug Zongker55d93282011-01-25 17:03:34 -08001084 self.sha1 = sha1(data).hexdigest()
1085
1086 @classmethod
1087 def FromLocalFile(cls, name, diskname):
1088 f = open(diskname, "rb")
1089 data = f.read()
1090 f.close()
1091 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001092
1093 def WriteToTemp(self):
1094 t = tempfile.NamedTemporaryFile()
1095 t.write(self.data)
1096 t.flush()
1097 return t
1098
Geremy Condra36bd3652014-02-06 19:45:10 -08001099 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001100 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001101
1102DIFF_PROGRAM_BY_EXT = {
1103 ".gz" : "imgdiff",
1104 ".zip" : ["imgdiff", "-z"],
1105 ".jar" : ["imgdiff", "-z"],
1106 ".apk" : ["imgdiff", "-z"],
1107 ".img" : "imgdiff",
1108 }
1109
1110class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001111 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001112 self.tf = tf
1113 self.sf = sf
1114 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001115 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001116
1117 def ComputePatch(self):
1118 """Compute the patch (as a string of data) needed to turn sf into
1119 tf. Returns the same tuple as GetPatch()."""
1120
1121 tf = self.tf
1122 sf = self.sf
1123
Doug Zongker24cd2802012-08-14 16:36:15 -07001124 if self.diff_program:
1125 diff_program = self.diff_program
1126 else:
1127 ext = os.path.splitext(tf.name)[1]
1128 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001129
1130 ttemp = tf.WriteToTemp()
1131 stemp = sf.WriteToTemp()
1132
1133 ext = os.path.splitext(tf.name)[1]
1134
1135 try:
1136 ptemp = tempfile.NamedTemporaryFile()
1137 if isinstance(diff_program, list):
1138 cmd = copy.copy(diff_program)
1139 else:
1140 cmd = [diff_program]
1141 cmd.append(stemp.name)
1142 cmd.append(ttemp.name)
1143 cmd.append(ptemp.name)
1144 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001145 err = []
1146 def run():
1147 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001148 if e:
1149 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001150 th = threading.Thread(target=run)
1151 th.start()
1152 th.join(timeout=300) # 5 mins
1153 if th.is_alive():
Anthony Kingc713d762015-11-03 00:23:11 +00001154 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001155 p.terminate()
1156 th.join(5)
1157 if th.is_alive():
1158 p.kill()
1159 th.join()
1160
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001161 if err or p.returncode != 0:
Anthony Kingc713d762015-11-03 00:23:11 +00001162 print("WARNING: failure running %s:\n%s\n" % (
1163 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001164 self.patch = None
1165 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001166 diff = ptemp.read()
1167 finally:
1168 ptemp.close()
1169 stemp.close()
1170 ttemp.close()
1171
1172 self.patch = diff
1173 return self.tf, self.sf, self.patch
1174
1175
1176 def GetPatch(self):
1177 """Return a tuple (target_file, source_file, patch_data).
1178 patch_data may be None if ComputePatch hasn't been called, or if
1179 computing the patch failed."""
1180 return self.tf, self.sf, self.patch
1181
1182
1183def ComputeDifferences(diffs):
1184 """Call ComputePatch on all the Difference objects in 'diffs'."""
Anthony Kingc713d762015-11-03 00:23:11 +00001185 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001186
1187 # Do the largest files first, to try and reduce the long-pole effect.
1188 by_size = [(i.tf.size, i) for i in diffs]
1189 by_size.sort(reverse=True)
1190 by_size = [i[1] for i in by_size]
1191
1192 lock = threading.Lock()
1193 diff_iter = iter(by_size) # accessed under lock
1194
1195 def worker():
1196 try:
1197 lock.acquire()
1198 for d in diff_iter:
1199 lock.release()
1200 start = time.time()
1201 d.ComputePatch()
1202 dur = time.time() - start
1203 lock.acquire()
1204
1205 tf, sf, patch = d.GetPatch()
1206 if sf.name == tf.name:
1207 name = tf.name
1208 else:
1209 name = "%s (%s)" % (tf.name, sf.name)
1210 if patch is None:
Anthony Kingc713d762015-11-03 00:23:11 +00001211 print("patching failed! %s" % name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001212 else:
Anthony Kingc713d762015-11-03 00:23:11 +00001213 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1214 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001215 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001216 except Exception as e:
Anthony Kingc713d762015-11-03 00:23:11 +00001217 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001218 raise
1219
1220 # start worker threads; wait for them all to finish.
1221 threads = [threading.Thread(target=worker)
1222 for i in range(OPTIONS.worker_threads)]
1223 for th in threads:
1224 th.start()
1225 while threads:
1226 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001227
1228
Dan Albert8b72aef2015-03-23 19:13:21 -07001229class BlockDifference(object):
1230 def __init__(self, partition, tgt, src=None, check_first_block=False,
1231 version=None):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001232 self.tgt = tgt
1233 self.src = src
1234 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001235 self.check_first_block = check_first_block
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001236
Tao Bao5ece99d2015-05-12 11:42:31 -07001237 # Due to http://b/20939131, check_first_block is disabled temporarily.
1238 assert not self.check_first_block
1239
Tao Baodd2a5892015-03-12 12:32:37 -07001240 if version is None:
1241 version = 1
1242 if OPTIONS.info_dict:
1243 version = max(
1244 int(i) for i in
1245 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
1246 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001247
1248 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Michael Runge910b0052015-02-11 19:28:08 -08001249 version=self.version)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001250 tmpdir = tempfile.mkdtemp()
1251 OPTIONS.tempfiles.append(tmpdir)
1252 self.path = os.path.join(tmpdir, partition)
1253 b.Compute(self.path)
1254
Tao Baoe09359a2015-10-13 16:37:12 -07001255 if src is None:
1256 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1257 else:
1258 _, self.device = GetTypeAndDevice("/" + partition,
1259 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001260
1261 def WriteScript(self, script, output_zip, progress=None):
1262 if not self.src:
1263 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001264 script.Print("Patching %s image unconditionally..." % (self.partition,))
1265 else:
1266 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001267
Dan Albert8b72aef2015-03-23 19:13:21 -07001268 if progress:
1269 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001270 self._WriteUpdate(script, output_zip)
Tao Bao5fcaaef2015-06-01 13:40:49 -07001271 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001272
1273 def WriteVerifyScript(self, script):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001274 partition = self.partition
Jesse Zhao75bcea02015-01-06 10:59:53 -08001275 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001276 script.Print("Image %s will be patched unconditionally." % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001277 else:
Tao Bao5ece99d2015-05-12 11:42:31 -07001278 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1279 ranges_str = ranges.to_string_raw()
Michael Runge910b0052015-02-11 19:28:08 -08001280 if self.version >= 3:
Sami Tolvanene09d0962015-04-24 11:54:01 +01001281 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1282 'block_image_verify("%s", '
Michael Runge910b0052015-02-11 19:28:08 -08001283 'package_extract_file("%s.transfer.list"), '
Sami Tolvanene09d0962015-04-24 11:54:01 +01001284 '"%s.new.dat", "%s.patch.dat")) then') % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001285 self.device, ranges_str, self.src.TotalSha1(),
Sami Tolvanene09d0962015-04-24 11:54:01 +01001286 self.device, partition, partition, partition))
Michael Runge910b0052015-02-11 19:28:08 -08001287 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001288 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
Tao Bao5ece99d2015-05-12 11:42:31 -07001289 self.device, ranges_str, self.src.TotalSha1()))
Tao Baodd2a5892015-03-12 12:32:37 -07001290 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001291 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001292
Tao Baodd2a5892015-03-12 12:32:37 -07001293 # When generating incrementals for the system and vendor partitions,
1294 # explicitly check the first block (which contains the superblock) of
1295 # the partition to see if it's what we expect. If this check fails,
1296 # give an explicit log message about the partition having been
1297 # remounted R/W (the most likely explanation) and the need to flash to
1298 # get OTAs working again.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001299 if self.check_first_block:
1300 self._CheckFirstBlock(script)
1301
Tao Baodd2a5892015-03-12 12:32:37 -07001302 # Abort the OTA update. Note that the incremental OTA cannot be applied
1303 # even if it may match the checksum of the target partition.
1304 # a) If version < 3, operations like move and erase will make changes
1305 # unconditionally and damage the partition.
1306 # b) If version >= 3, it won't even reach here.
1307 script.AppendExtra(('abort("%s partition has unexpected contents");\n'
1308 'endif;') % (partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001309
Tao Bao5fcaaef2015-06-01 13:40:49 -07001310 def _WritePostInstallVerifyScript(self, script):
1311 partition = self.partition
1312 script.Print('Verifying the updated %s image...' % (partition,))
1313 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1314 ranges = self.tgt.care_map
1315 ranges_str = ranges.to_string_raw()
1316 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1317 self.device, ranges_str,
1318 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001319
1320 # Bug: 20881595
1321 # Verify that extended blocks are really zeroed out.
1322 if self.tgt.extended:
1323 ranges_str = self.tgt.extended.to_string_raw()
1324 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1325 self.device, ranges_str,
1326 self._HashZeroBlocks(self.tgt.extended.size())))
1327 script.Print('Verified the updated %s image.' % (partition,))
1328 script.AppendExtra(
1329 'else\n'
1330 ' abort("%s partition has unexpected non-zero contents after OTA '
1331 'update");\n'
1332 'endif;' % (partition,))
1333 else:
1334 script.Print('Verified the updated %s image.' % (partition,))
1335
Tao Bao5fcaaef2015-06-01 13:40:49 -07001336 script.AppendExtra(
1337 'else\n'
1338 ' abort("%s partition has unexpected contents after OTA update");\n'
1339 'endif;' % (partition,))
1340
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001341 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001342 ZipWrite(output_zip,
1343 '{}.transfer.list'.format(self.path),
1344 '{}.transfer.list'.format(self.partition))
1345 ZipWrite(output_zip,
1346 '{}.new.dat'.format(self.path),
1347 '{}.new.dat'.format(self.partition))
1348 ZipWrite(output_zip,
1349 '{}.patch.dat'.format(self.path),
1350 '{}.patch.dat'.format(self.partition),
1351 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001352
Dan Albert8e0178d2015-01-27 15:53:15 -08001353 call = ('block_image_update("{device}", '
1354 'package_extract_file("{partition}.transfer.list"), '
1355 '"{partition}.new.dat", "{partition}.patch.dat");\n'.format(
1356 device=self.device, partition=self.partition))
Dan Albert8b72aef2015-03-23 19:13:21 -07001357 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001358
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001360 data = source.ReadRangeSet(ranges)
1361 ctx = sha1()
1362
1363 for p in data:
1364 ctx.update(p)
1365
1366 return ctx.hexdigest()
1367
Tao Bao2fd2c9b2015-07-09 17:37:49 -07001368 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1369 """Return the hash value for all zero blocks."""
1370 zero_block = '\x00' * 4096
1371 ctx = sha1()
1372 for _ in range(num_blocks):
1373 ctx.update(zero_block)
1374
1375 return ctx.hexdigest()
1376
Tao Bao5ece99d2015-05-12 11:42:31 -07001377 # TODO(tbao): Due to http://b/20939131, block 0 may be changed without
1378 # remounting R/W. Will change the checking to a finer-grained way to
1379 # mask off those bits.
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001380 def _CheckFirstBlock(self, script):
Dan Albert8b72aef2015-03-23 19:13:21 -07001381 r = rangelib.RangeSet((0, 1))
1382 srchash = self._HashBlocks(self.src, r)
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001383
1384 script.AppendExtra(('(range_sha1("%s", "%s") == "%s") || '
1385 'abort("%s has been remounted R/W; '
1386 'reflash device to reenable OTA updates");')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001387 % (self.device, r.to_string_raw(), srchash,
Sami Tolvanendd67a292014-12-09 16:40:34 +00001388 self.device))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001389
1390DataImage = blockimgdiff.DataImage
1391
1392
Doug Zongker96a57e72010-09-26 14:57:41 -07001393# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001394PARTITION_TYPES = {
1395 "yaffs2": "MTD",
1396 "mtd": "MTD",
1397 "ext4": "EMMC",
1398 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001399 "f2fs": "EMMC",
Brandon Bennett470ea4c2011-11-19 16:02:04 -07001400 "squashfs": "EMMC",
1401 "ext2": "EMMC",
1402 "ext3": "EMMC",
Stephen Bird9f657582015-10-08 02:05:06 -07001403 "vfat": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001404}
Doug Zongker96a57e72010-09-26 14:57:41 -07001405
1406def GetTypeAndDevice(mount_point, info):
1407 fstab = info["fstab"]
1408 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001409 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1410 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001411 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001412 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001413
1414
1415def ParseCertificate(data):
1416 """Parse a PEM-format certificate."""
Anthony Kingc713d762015-11-03 00:23:11 +00001417 from codecs import decode
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001418 cert = []
1419 save = False
1420 for line in data.split("\n"):
1421 if "--END CERTIFICATE--" in line:
1422 break
1423 if save:
Anthony Kingc713d762015-11-03 00:23:11 +00001424 l = line.encode() if hasattr(line, 'encode') else line
1425 cert.append(l)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001426 if "--BEGIN CERTIFICATE--" in line:
1427 save = True
Anthony Kingc713d762015-11-03 00:23:11 +00001428 cert = decode(b"".join(cert), 'base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001429 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001430
Doug Zongker412c02f2014-02-13 10:58:24 -08001431def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1432 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001433 """Generate a binary patch that creates the recovery image starting
1434 with the boot image. (Most of the space in these images is just the
1435 kernel, which is identical for the two, so the resulting patch
1436 should be efficient.) Add it to the output zip, along with a shell
1437 script that is run from init.rc on first boot to actually do the
1438 patching and install the new recovery image.
1439
1440 recovery_img and boot_img should be File objects for the
1441 corresponding images. info should be the dictionary returned by
1442 common.LoadInfoDict() on the input target_files.
1443 """
1444
Doug Zongker412c02f2014-02-13 10:58:24 -08001445 if info_dict is None:
1446 info_dict = OPTIONS.info_dict
1447
Doug Zongkerc9253822014-02-04 12:17:58 -08001448 diff_program = ["imgdiff"]
1449 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1450 if os.path.exists(path):
1451 diff_program.append("-b")
1452 diff_program.append(path)
1453 bonus_args = "-b /system/etc/recovery-resource.dat"
1454 else:
1455 bonus_args = ""
1456
1457 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1458 _, _, patch = d.ComputePatch()
1459 output_sink("recovery-from-boot.p", patch)
1460
Dan Albertebb19aa2015-03-27 19:11:53 -07001461 try:
Tao Baoe09359a2015-10-13 16:37:12 -07001462 # The following GetTypeAndDevice()s need to use the path in the target
1463 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001464 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1465 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1466 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001467 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001468
1469 sh = """#!/system/bin/sh
Ricardo Cerqueira83197082014-06-19 01:45:15 +01001470if [ -f /system/etc/recovery-transform.sh ]; then
1471 exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s
1472fi
1473
Doug Zongkerc9253822014-02-04 12:17:58 -08001474if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1475 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"
1476else
1477 log -t recovery "Recovery image already installed"
1478fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001479""" % {'boot_size': boot_img.size,
1480 'boot_sha1': boot_img.sha1,
1481 'recovery_size': recovery_img.size,
1482 'recovery_sha1': recovery_img.sha1,
1483 'boot_type': boot_type,
1484 'boot_device': boot_device,
1485 'recovery_type': recovery_type,
1486 'recovery_device': recovery_device,
1487 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001488
1489 # The install script location moved from /system/etc to /system/bin
1490 # in the L release. Parse the init.rc file to find out where the
1491 # target-files expects it to be, and put it there.
1492 sh_location = "etc/install-recovery.sh"
1493 try:
1494 with open(os.path.join(input_dir, "BOOT", "RAMDISK", "init.rc")) as f:
1495 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001496 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001497 if m:
1498 sh_location = m.group(1)
Anthony Kingc713d762015-11-03 00:23:11 +00001499 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001500 break
Dan Albert8b72aef2015-03-23 19:13:21 -07001501 except (OSError, IOError) as e:
Anthony Kingc713d762015-11-03 00:23:11 +00001502 print("failed to read init.rc: %s" % e)
Doug Zongkerc9253822014-02-04 12:17:58 -08001503
1504 output_sink(sh_location, sh)