blob: 652fadf56ee1d1b10c5daf342441263c2ed6f666 [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
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __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
35
Tao Baof3282b42015-04-01 11:21:55 -070036from hashlib import sha1 as sha1
Doug Zongker55d93282011-01-25 17:03:34 -080037
Doug Zongkereef39442009-04-02 12:14:19 -070038
Dan Albert8b72aef2015-03-23 19:13:21 -070039class Options(object):
40 def __init__(self):
41 platform_search_path = {
42 "linux2": "out/host/linux-x86",
43 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070044 }
Doug Zongker85448772014-09-09 14:59:20 -070045
Dan Albert8b72aef2015-03-23 19:13:21 -070046 self.search_path = platform_search_path.get(sys.platform, None)
47 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080048 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070049 self.extra_signapk_args = []
50 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080051 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070052 self.public_key_suffix = ".x509.pem"
53 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070054 # use otatools built boot_signer by default
55 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070056 self.boot_signer_args = []
57 self.verity_signer_path = None
58 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070059 self.verbose = False
60 self.tempfiles = []
61 self.device_specific = None
62 self.extras = {}
63 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070064 self.source_info_dict = None
65 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070067 # Stash size cannot exceed cache_size * threshold.
68 self.cache_size = None
69 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070070
71
72OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070073
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080074
75# Values for "certificate" in apkcerts that mean special things.
76SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
77
Tianjie Xu209db462016-05-24 17:34:52 -070078class ErrorCode(object):
79 """Define error_codes for failures that happen during the actual
80 update package installation.
81
82 Error codes 0-999 are reserved for failures before the package
83 installation (i.e. low battery, package verification failure).
84 Detailed code in 'bootable/recovery/error_code.h' """
85
86 SYSTEM_VERIFICATION_FAILURE = 1000
87 SYSTEM_UPDATE_FAILURE = 1001
88 SYSTEM_UNEXPECTED_CONTENTS = 1002
89 SYSTEM_NONZERO_CONTENTS = 1003
90 SYSTEM_RECOVER_FAILURE = 1004
91 VENDOR_VERIFICATION_FAILURE = 2000
92 VENDOR_UPDATE_FAILURE = 2001
93 VENDOR_UNEXPECTED_CONTENTS = 2002
94 VENDOR_NONZERO_CONTENTS = 2003
95 VENDOR_RECOVER_FAILURE = 2004
96 OEM_PROP_MISMATCH = 3000
97 FINGERPRINT_MISMATCH = 3001
98 THUMBPRINT_MISMATCH = 3002
99 OLDER_BUILD = 3003
100 DEVICE_MISMATCH = 3004
101 BAD_PATCH_FILE = 3005
102 INSUFFICIENT_CACHE_SPACE = 3006
103 TUNE_PARTITION_FAILURE = 3007
104 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800105
Dan Albert8b72aef2015-03-23 19:13:21 -0700106class ExternalError(RuntimeError):
107 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700108
109
Tao Bao39451582017-05-04 11:10:47 -0700110def Run(args, verbose=None, **kwargs):
111 """Create and return a subprocess.Popen object.
112
113 Caller can specify if the command line should be printed. The global
114 OPTIONS.verbose will be used if not specified.
115 """
116 if verbose is None:
117 verbose = OPTIONS.verbose
118 if verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800119 print(" running: ", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700120 return subprocess.Popen(args, **kwargs)
121
122
Ying Wang7e6d4e42010-12-13 16:25:36 -0800123def CloseInheritedPipes():
124 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
125 before doing other work."""
126 if platform.system() != "Darwin":
127 return
128 for d in range(3, 1025):
129 try:
130 stat = os.fstat(d)
131 if stat is not None:
132 pipebit = stat[0] & 0x1000
133 if pipebit != 0:
134 os.close(d)
135 except OSError:
136 pass
137
138
Tao Bao2c15d9e2015-07-09 11:51:16 -0700139def LoadInfoDict(input_file, input_dir=None):
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700140 """Read and parse the META/misc_info.txt key/value pairs from the
141 input target files and return a dict."""
142
Doug Zongkerc9253822014-02-04 12:17:58 -0800143 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700144 if isinstance(input_file, zipfile.ZipFile):
145 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800146 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700147 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800148 try:
149 with open(path) as f:
150 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700151 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800152 if e.errno == errno.ENOENT:
153 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800154
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700155 try:
Michael Runge6e836112014-04-15 17:40:21 -0700156 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700157 except KeyError:
Tao Bao6cd54732017-02-27 15:12:05 -0800158 raise ValueError("can't find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700159
Tao Bao6cd54732017-02-27 15:12:05 -0800160 assert "recovery_api_version" in d
Tao Baod1de6f32017-03-01 16:38:48 -0800161 assert "fstab_version" in d
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800162
Tao Bao84e75682015-07-19 02:38:53 -0700163 # A few properties are stored as links to the files in the out/ directory.
164 # It works fine with the build system. However, they are no longer available
165 # when (re)generating from target_files zip. If input_dir is not None, we
166 # are doing repacking. Redirect those properties to the actual files in the
167 # unzipped directory.
Tao Bao2c15d9e2015-07-09 11:51:16 -0700168 if input_dir is not None:
Stephen Smalleyd3a803e2015-08-04 14:59:06 -0400169 # We carry a copy of file_contexts.bin under META/. If not available,
170 # search BOOT/RAMDISK/. Note that sometimes we may need a different file
Tao Bao84e75682015-07-19 02:38:53 -0700171 # to build images than the one running on device, such as when enabling
172 # system_root_image. In that case, we must have the one for image
173 # generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700174 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
175 fc_config = os.path.join(input_dir, "META", fc_basename)
Tao Bao84e75682015-07-19 02:38:53 -0700176 if d.get("system_root_image") == "true":
177 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700178 if not os.path.exists(fc_config):
Tao Bao79735a62015-08-28 10:52:03 -0700179 fc_config = os.path.join(input_dir, "BOOT", "RAMDISK", fc_basename)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700180 if not os.path.exists(fc_config):
181 fc_config = None
182
183 if fc_config:
184 d["selinux_fc"] = fc_config
185
Tao Bao84e75682015-07-19 02:38:53 -0700186 # Similarly we need to redirect "ramdisk_dir" and "ramdisk_fs_config".
187 if d.get("system_root_image") == "true":
188 d["ramdisk_dir"] = os.path.join(input_dir, "ROOT")
189 d["ramdisk_fs_config"] = os.path.join(
190 input_dir, "META", "root_filesystem_config.txt")
191
Tao Baof54216f2016-03-29 15:12:37 -0700192 # Redirect {system,vendor}_base_fs_file.
193 if "system_base_fs_file" in d:
194 basename = os.path.basename(d["system_base_fs_file"])
195 system_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700196 if os.path.exists(system_base_fs_file):
197 d["system_base_fs_file"] = system_base_fs_file
198 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800199 print("Warning: failed to find system base fs file: %s" % (
200 system_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700201 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700202
203 if "vendor_base_fs_file" in d:
204 basename = os.path.basename(d["vendor_base_fs_file"])
205 vendor_base_fs_file = os.path.join(input_dir, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700206 if os.path.exists(vendor_base_fs_file):
207 d["vendor_base_fs_file"] = vendor_base_fs_file
208 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800209 print("Warning: failed to find vendor base fs file: %s" % (
210 vendor_base_fs_file,))
Tao Baob079b502016-05-03 08:01:19 -0700211 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700212
Doug Zongker37974732010-09-16 17:44:38 -0700213 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800214 data = read_helper("META/imagesizes.txt")
Doug Zongker37974732010-09-16 17:44:38 -0700215 for line in data.split("\n"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700216 if not line:
217 continue
Doug Zongker1684d9c2010-09-17 07:44:38 -0700218 name, value = line.split(" ", 1)
Dan Albert8b72aef2015-03-23 19:13:21 -0700219 if not value:
220 continue
Doug Zongker37974732010-09-16 17:44:38 -0700221 if name == "blocksize":
222 d[name] = value
223 else:
224 d[name + "_size"] = value
225 except KeyError:
226 pass
227
228 def makeint(key):
229 if key in d:
230 d[key] = int(d[key], 0)
231
232 makeint("recovery_api_version")
233 makeint("blocksize")
234 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700235 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700236 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700237 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700238 makeint("recovery_size")
239 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800240 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700241
Tianjie Xucfa86222016-03-07 16:31:19 -0800242 system_root_image = d.get("system_root_image", None) == "true"
243 if d.get("no_recovery", None) != "true":
244 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao48550cc2015-11-19 17:05:46 -0800245 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
Tianjie Xucfa86222016-03-07 16:31:19 -0800246 recovery_fstab_path, system_root_image)
247 elif d.get("recovery_as_boot", None) == "true":
248 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
249 d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"],
250 recovery_fstab_path, system_root_image)
251 else:
252 d["fstab"] = None
253
Doug Zongkerc9253822014-02-04 12:17:58 -0800254 d["build.prop"] = LoadBuildProp(read_helper)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700255 return d
256
Tao Baod1de6f32017-03-01 16:38:48 -0800257
Doug Zongkerc9253822014-02-04 12:17:58 -0800258def LoadBuildProp(read_helper):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700259 try:
Doug Zongkerc9253822014-02-04 12:17:58 -0800260 data = read_helper("SYSTEM/build.prop")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700261 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800262 print("Warning: could not find SYSTEM/build.prop in %s" % (zip,))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700263 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700264 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700265
Tao Baod1de6f32017-03-01 16:38:48 -0800266
Michael Runge6e836112014-04-15 17:40:21 -0700267def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700268 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700269 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700270 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700271 if not line or line.startswith("#"):
272 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700273 if "=" in line:
274 name, value = line.split("=", 1)
275 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700276 return d
277
Tao Baod1de6f32017-03-01 16:38:48 -0800278
Tianjie Xucfa86222016-03-07 16:31:19 -0800279def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
280 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700281 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800282 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 self.mount_point = mount_point
284 self.fs_type = fs_type
285 self.device = device
286 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700287 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700288
289 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800290 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700291 except KeyError:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800292 print("Warning: could not find {}".format(recovery_fstab_path))
Jeff Davidson033fbe22011-10-26 18:08:09 -0700293 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700294
Tao Baod1de6f32017-03-01 16:38:48 -0800295 assert fstab_version == 2
296
297 d = {}
298 for line in data.split("\n"):
299 line = line.strip()
300 if not line or line.startswith("#"):
301 continue
302
303 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
304 pieces = line.split()
305 if len(pieces) != 5:
306 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
307
308 # Ignore entries that are managed by vold.
309 options = pieces[4]
310 if "voldmanaged=" in options:
311 continue
312
313 # It's a good line, parse it.
314 length = 0
315 options = options.split(",")
316 for i in options:
317 if i.startswith("length="):
318 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800319 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800320 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800322
Tao Baod1de6f32017-03-01 16:38:48 -0800323 mount_flags = pieces[3]
324 # Honor the SELinux context if present.
325 context = None
326 for i in mount_flags.split(","):
327 if i.startswith("context="):
328 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800329
Tao Baod1de6f32017-03-01 16:38:48 -0800330 mount_point = pieces[1]
331 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
332 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700334 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700335 # system. Other areas assume system is always at "/system" so point /system
336 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700337 if system_root_image:
338 assert not d.has_key("/system") and d.has_key("/")
339 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700340 return d
341
342
Doug Zongker37974732010-09-16 17:44:38 -0700343def DumpInfoDict(d):
344 for k, v in sorted(d.items()):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800345 print("%-25s = (%s) %s" % (k, type(v).__name__, v))
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700346
Dan Albert8b72aef2015-03-23 19:13:21 -0700347
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700348def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800349 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700350 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700351
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700352 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800353 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
354 we are building a two-step special image (i.e. building a recovery image to
355 be loaded into /boot in two-step OTAs).
356
357 Return the image data, or None if sourcedir does not appear to contains files
358 for building the requested image.
359 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700360
361 def make_ramdisk():
362 ramdisk_img = tempfile.NamedTemporaryFile()
363
364 if os.access(fs_config_file, os.F_OK):
365 cmd = ["mkbootfs", "-f", fs_config_file,
366 os.path.join(sourcedir, "RAMDISK")]
367 else:
368 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
369 p1 = Run(cmd, stdout=subprocess.PIPE)
370 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
371
372 p2.wait()
373 p1.wait()
374 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
375 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
376
377 return ramdisk_img
378
379 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
380 return None
381
382 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700383 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700384
Doug Zongkerd5131602012-08-02 14:46:42 -0700385 if info_dict is None:
386 info_dict = OPTIONS.info_dict
387
Doug Zongkereef39442009-04-02 12:14:19 -0700388 img = tempfile.NamedTemporaryFile()
389
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700390 if has_ramdisk:
391 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700392
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800393 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
394 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
395
396 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700397
Benoit Fradina45a8682014-07-14 21:00:43 +0200398 fn = os.path.join(sourcedir, "second")
399 if os.access(fn, os.F_OK):
400 cmd.append("--second")
401 cmd.append(fn)
402
Doug Zongker171f1cd2009-06-15 22:36:37 -0700403 fn = os.path.join(sourcedir, "cmdline")
404 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700405 cmd.append("--cmdline")
406 cmd.append(open(fn).read().rstrip("\n"))
407
408 fn = os.path.join(sourcedir, "base")
409 if os.access(fn, os.F_OK):
410 cmd.append("--base")
411 cmd.append(open(fn).read().rstrip("\n"))
412
Ying Wang4de6b5b2010-08-25 14:29:34 -0700413 fn = os.path.join(sourcedir, "pagesize")
414 if os.access(fn, os.F_OK):
415 cmd.append("--pagesize")
416 cmd.append(open(fn).read().rstrip("\n"))
417
Doug Zongkerd5131602012-08-02 14:46:42 -0700418 args = info_dict.get("mkbootimg_args", None)
419 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700420 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700421
Sami Tolvanen3303d902016-03-15 16:49:30 +0000422 args = info_dict.get("mkbootimg_version_args", None)
423 if args and args.strip():
424 cmd.extend(shlex.split(args))
425
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700426 if has_ramdisk:
427 cmd.extend(["--ramdisk", ramdisk_img.name])
428
Tao Baod95e9fd2015-03-29 23:07:41 -0700429 img_unsigned = None
430 if info_dict.get("vboot", None):
431 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700432 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700433 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700434 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700435
436 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700437 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700438 assert p.returncode == 0, "mkbootimg of %s image failed" % (
439 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700440
Sami Tolvanen8b3f08b2015-04-07 15:08:59 +0100441 if (info_dict.get("boot_signer", None) == "true" and
442 info_dict.get("verity_key", None)):
Tao Baod42e97e2016-11-30 12:11:57 -0800443 # Hard-code the path as "/boot" for two-step special recovery image (which
444 # will be loaded into /boot during the two-step OTA).
445 if two_step_image:
446 path = "/boot"
447 else:
448 path = "/" + os.path.basename(sourcedir).lower()
Baligh Uddin601ddea2015-06-09 15:48:14 -0700449 cmd = [OPTIONS.boot_signer_path]
450 cmd.extend(OPTIONS.boot_signer_args)
451 cmd.extend([path, img.name,
452 info_dict["verity_key"] + ".pk8",
453 info_dict["verity_key"] + ".x509.pem", img.name])
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700454 p = Run(cmd, stdout=subprocess.PIPE)
455 p.communicate()
456 assert p.returncode == 0, "boot_signer of %s image failed" % path
457
Tao Baod95e9fd2015-03-29 23:07:41 -0700458 # Sign the image if vboot is non-empty.
459 elif info_dict.get("vboot", None):
460 path = "/" + os.path.basename(sourcedir).lower()
461 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800462 # We have switched from the prebuilt futility binary to using the tool
463 # (futility-host) built from the source. Override the setting in the old
464 # TF.zip.
465 futility = info_dict["futility"]
466 if futility.startswith("prebuilts/"):
467 futility = "futility-host"
468 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700469 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700470 info_dict["vboot_key"] + ".vbprivk",
471 info_dict["vboot_subkey"] + ".vbprivk",
472 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700473 img.name]
474 p = Run(cmd, stdout=subprocess.PIPE)
475 p.communicate()
476 assert p.returncode == 0, "vboot_signer of %s image failed" % path
477
Tao Baof3282b42015-04-01 11:21:55 -0700478 # Clean up the temp files.
479 img_unsigned.close()
480 img_keyblock.close()
481
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400482 # AVB: if enabled, calculate and add hash to boot.img.
Tao Baob31b94e2016-09-29 21:59:06 -0700483 if info_dict.get("board_avb_enable", None) == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700484 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
485 part_size = info_dict["boot_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400486 cmd = [avbtool, "add_hash_footer", "--image", img.name,
487 "--partition_size", str(part_size), "--partition_name", "boot"]
Tao Bao3ebfdde2017-05-23 23:06:55 -0700488 cmd.extend(shlex.split(info_dict["avb_signing_args"]))
489 args = info_dict.get("board_avb_boot_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400490 if args and args.strip():
491 cmd.extend(shlex.split(args))
492 p = Run(cmd, stdout=subprocess.PIPE)
493 p.communicate()
494 assert p.returncode == 0, "avbtool add_hash_footer of %s failed" % (
495 os.path.basename(OPTIONS.input_tmp))
David Zeuthend995f4b2016-01-29 16:59:17 -0500496
497 img.seek(os.SEEK_SET, 0)
498 data = img.read()
499
500 if has_ramdisk:
501 ramdisk_img.close()
502 img.close()
503
504 return data
505
506
Doug Zongkerd5131602012-08-02 14:46:42 -0700507def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800508 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700509 """Return a File object with the desired bootable image.
510
511 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
512 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
513 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700514
Doug Zongker55d93282011-01-25 17:03:34 -0800515 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
516 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800517 print("using prebuilt %s from BOOTABLE_IMAGES..." % (prebuilt_name,))
Doug Zongker55d93282011-01-25 17:03:34 -0800518 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700519
520 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
521 if os.path.exists(prebuilt_path):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800522 print("using prebuilt %s from IMAGES..." % (prebuilt_name,))
Doug Zongker6f1d0312014-08-22 08:07:12 -0700523 return File.FromLocalFile(name, prebuilt_path)
524
Tao Bao89fbb0f2017-01-10 10:47:58 -0800525 print("building image from target_files %s..." % (tree_subdir,))
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700526
527 if info_dict is None:
528 info_dict = OPTIONS.info_dict
529
530 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800531 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
532 # for recovery.
533 has_ramdisk = (info_dict.get("system_root_image") != "true" or
534 prebuilt_name != "boot.img" or
535 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536
Doug Zongker6f1d0312014-08-22 08:07:12 -0700537 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400538 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
539 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800540 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700541 if data:
542 return File(name, data)
543 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800544
Doug Zongkereef39442009-04-02 12:14:19 -0700545
Doug Zongker75f17362009-12-08 13:46:44 -0800546def UnzipTemp(filename, pattern=None):
Doug Zongker55d93282011-01-25 17:03:34 -0800547 """Unzip the given archive into a temporary directory and return the name.
548
549 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a
550 temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
551
552 Returns (tempdir, zipobj) where zipobj is a zipfile.ZipFile (of the
553 main file), open for reading.
554 """
Doug Zongkereef39442009-04-02 12:14:19 -0700555
556 tmp = tempfile.mkdtemp(prefix="targetfiles-")
557 OPTIONS.tempfiles.append(tmp)
Doug Zongker55d93282011-01-25 17:03:34 -0800558
559 def unzip_to_dir(filename, dirname):
560 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
561 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800562 cmd.extend(pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800563 p = Run(cmd, stdout=subprocess.PIPE)
564 p.communicate()
565 if p.returncode != 0:
566 raise ExternalError("failed to unzip input target-files \"%s\"" %
567 (filename,))
568
569 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
570 if m:
571 unzip_to_dir(m.group(1), tmp)
572 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
573 filename = m.group(1)
574 else:
575 unzip_to_dir(filename, tmp)
576
577 return tmp, zipfile.ZipFile(filename, "r")
Doug Zongkereef39442009-04-02 12:14:19 -0700578
579
580def GetKeyPasswords(keylist):
581 """Given a list of keys, prompt the user to enter passwords for
582 those which require them. Return a {key: password} dict. password
583 will be None if the key has no password."""
584
Doug Zongker8ce7c252009-05-22 13:34:54 -0700585 no_passwords = []
586 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700587 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700588 devnull = open("/dev/null", "w+b")
589 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800590 # We don't need a password for things that aren't really keys.
591 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700592 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700593 continue
594
T.R. Fullhart37e10522013-03-18 10:31:26 -0700595 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700596 "-inform", "DER", "-nocrypt"],
597 stdin=devnull.fileno(),
598 stdout=devnull.fileno(),
599 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700600 p.communicate()
601 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700602 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700603 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700604 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700605 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
606 "-inform", "DER", "-passin", "pass:"],
607 stdin=devnull.fileno(),
608 stdout=devnull.fileno(),
609 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700610 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700611 if p.returncode == 0:
612 # Encrypted key with empty string as password.
613 key_passwords[k] = ''
614 elif stderr.startswith('Error decrypting key'):
615 # Definitely encrypted key.
616 # It would have said "Error reading key" if it didn't parse correctly.
617 need_passwords.append(k)
618 else:
619 # Potentially, a type of key that openssl doesn't understand.
620 # We'll let the routines in signapk.jar handle it.
621 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700622 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700623
T.R. Fullhart37e10522013-03-18 10:31:26 -0700624 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700625 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700626 return key_passwords
627
628
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800629def GetMinSdkVersion(apk_name):
630 """Get the minSdkVersion delared in the APK. This can be both a decimal number
631 (API Level) or a codename.
632 """
633
634 p = Run(["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE)
635 output, err = p.communicate()
636 if err:
637 raise ExternalError("Failed to obtain minSdkVersion: aapt return code %s"
638 % (p.returncode,))
639
640 for line in output.split("\n"):
641 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'
642 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
643 if m:
644 return m.group(1)
645 raise ExternalError("No minSdkVersion returned by aapt")
646
647
648def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
649 """Get the minSdkVersion declared in the APK as a number (API Level). If
650 minSdkVersion is set to a codename, it is translated to a number using the
651 provided map.
652 """
653
654 version = GetMinSdkVersion(apk_name)
655 try:
656 return int(version)
657 except ValueError:
658 # Not a decimal number. Codename?
659 if version in codename_to_api_level_map:
660 return codename_to_api_level_map[version]
661 else:
662 raise ExternalError("Unknown minSdkVersion: '%s'. Known codenames: %s"
663 % (version, codename_to_api_level_map))
664
665
666def SignFile(input_name, output_name, key, password, min_api_level=None,
667 codename_to_api_level_map=dict(),
668 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700669 """Sign the input_name zip/jar/apk, producing output_name. Use the
670 given key and password (the latter may be None if the key does not
671 have a password.
672
Doug Zongker951495f2009-08-14 12:44:19 -0700673 If whole_file is true, use the "-w" option to SignApk to embed a
674 signature that covers the whole file in the archive comment of the
675 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800676
677 min_api_level is the API Level (int) of the oldest platform this file may end
678 up on. If not specified for an APK, the API Level is obtained by interpreting
679 the minSdkVersion attribute of the APK's AndroidManifest.xml.
680
681 codename_to_api_level_map is needed to translate the codename which may be
682 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700683 """
Doug Zongker951495f2009-08-14 12:44:19 -0700684
Alex Klyubin9667b182015-12-10 13:38:50 -0800685 java_library_path = os.path.join(
686 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
687
Tao Baoe95540e2016-11-08 12:08:53 -0800688 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
689 ["-Djava.library.path=" + java_library_path,
690 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
691 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700692 if whole_file:
693 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800694
695 min_sdk_version = min_api_level
696 if min_sdk_version is None:
697 if not whole_file:
698 min_sdk_version = GetMinSdkVersionInt(
699 input_name, codename_to_api_level_map)
700 if min_sdk_version is not None:
701 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
702
T.R. Fullhart37e10522013-03-18 10:31:26 -0700703 cmd.extend([key + OPTIONS.public_key_suffix,
704 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800705 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700706
707 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700708 if password is not None:
709 password += "\n"
710 p.communicate(password)
711 if p.returncode != 0:
712 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
713
Doug Zongkereef39442009-04-02 12:14:19 -0700714
Doug Zongker37974732010-09-16 17:44:38 -0700715def CheckSize(data, target, info_dict):
Doug Zongkereef39442009-04-02 12:14:19 -0700716 """Check the data string passed against the max size limit, if
717 any, for the given target. Raise exception if the data is too big.
718 Print a warning if the data is nearing the maximum size."""
Doug Zongkerc77a9ad2010-09-16 11:28:43 -0700719
Dan Albert8b72aef2015-03-23 19:13:21 -0700720 if target.endswith(".img"):
721 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700722 mount_point = "/" + target
723
Ying Wangf8824af2014-06-03 14:07:27 -0700724 fs_type = None
725 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700726 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -0700727 if mount_point == "/userdata":
728 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700729 p = info_dict["fstab"][mount_point]
730 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -0800731 device = p.device
732 if "/" in device:
733 device = device[device.rfind("/")+1:]
734 limit = info_dict.get(device + "_size", None)
Dan Albert8b72aef2015-03-23 19:13:21 -0700735 if not fs_type or not limit:
736 return
Doug Zongkereef39442009-04-02 12:14:19 -0700737
Andrew Boie0f9aec82012-02-14 09:32:52 -0800738 size = len(data)
739 pct = float(size) * 100.0 / limit
740 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
741 if pct >= 99.0:
742 raise ExternalError(msg)
743 elif pct >= 95.0:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800744 print("\n WARNING: %s\n" % (msg,))
Andrew Boie0f9aec82012-02-14 09:32:52 -0800745 elif OPTIONS.verbose:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800746 print(" ", msg)
Doug Zongkereef39442009-04-02 12:14:19 -0700747
748
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800749def ReadApkCerts(tf_zip):
750 """Given a target_files ZipFile, parse the META/apkcerts.txt file
751 and return a {package: cert} dict."""
752 certmap = {}
753 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
754 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700755 if not line:
756 continue
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800757 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
758 r'private_key="(.*)"$', line)
759 if m:
760 name, cert, privkey = m.groups()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700761 public_key_suffix_len = len(OPTIONS.public_key_suffix)
762 private_key_suffix_len = len(OPTIONS.private_key_suffix)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800763 if cert in SPECIAL_CERT_STRINGS and not privkey:
764 certmap[name] = cert
T.R. Fullhart37e10522013-03-18 10:31:26 -0700765 elif (cert.endswith(OPTIONS.public_key_suffix) and
766 privkey.endswith(OPTIONS.private_key_suffix) and
767 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
768 certmap[name] = cert[:-public_key_suffix_len]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800769 else:
770 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
771 return certmap
772
773
Doug Zongkereef39442009-04-02 12:14:19 -0700774COMMON_DOCSTRING = """
775 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700776 Prepend <dir>/bin to the list of places to search for binaries
777 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700778
Doug Zongker05d3dea2009-06-22 11:32:31 -0700779 -s (--device_specific) <file>
780 Path to the python module containing device-specific
781 releasetools code.
782
Doug Zongker8bec09e2009-11-30 15:37:14 -0800783 -x (--extra) <key=value>
784 Add a key/value pair to the 'extras' dict, which device-specific
785 extension code may look at.
786
Doug Zongkereef39442009-04-02 12:14:19 -0700787 -v (--verbose)
788 Show command lines being executed.
789
790 -h (--help)
791 Display this usage message and exit.
792"""
793
794def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -0800795 print(docstring.rstrip("\n"))
796 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -0700797
798
799def ParseOptions(argv,
800 docstring,
801 extra_opts="", extra_long_opts=(),
802 extra_option_handler=None):
803 """Parse the options in argv and return any arguments that aren't
804 flags. docstring is the calling module's docstring, to be displayed
805 for errors and -h. extra_opts and extra_long_opts are for flags
806 defined by the caller, which are processed by passing them to
807 extra_option_handler."""
808
809 try:
810 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800811 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -0800812 ["help", "verbose", "path=", "signapk_path=",
813 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -0700814 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -0700815 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
816 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -0800817 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -0700818 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -0700819 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -0700820 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -0800821 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -0700822 sys.exit(2)
823
Doug Zongkereef39442009-04-02 12:14:19 -0700824 for o, a in opts:
825 if o in ("-h", "--help"):
826 Usage(docstring)
827 sys.exit()
828 elif o in ("-v", "--verbose"):
829 OPTIONS.verbose = True
830 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700831 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700832 elif o in ("--signapk_path",):
833 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -0800834 elif o in ("--signapk_shared_library_path",):
835 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -0700836 elif o in ("--extra_signapk_args",):
837 OPTIONS.extra_signapk_args = shlex.split(a)
838 elif o in ("--java_path",):
839 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -0700840 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -0800841 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -0700842 elif o in ("--public_key_suffix",):
843 OPTIONS.public_key_suffix = a
844 elif o in ("--private_key_suffix",):
845 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -0800846 elif o in ("--boot_signer_path",):
847 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -0700848 elif o in ("--boot_signer_args",):
849 OPTIONS.boot_signer_args = shlex.split(a)
850 elif o in ("--verity_signer_path",):
851 OPTIONS.verity_signer_path = a
852 elif o in ("--verity_signer_args",):
853 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700854 elif o in ("-s", "--device_specific"):
855 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800856 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800857 key, value = a.split("=", 1)
858 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700859 else:
860 if extra_option_handler is None or not extra_option_handler(o, a):
861 assert False, "unknown option \"%s\"" % (o,)
862
Doug Zongker85448772014-09-09 14:59:20 -0700863 if OPTIONS.search_path:
864 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
865 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700866
867 return args
868
869
Tao Bao4c851b12016-09-19 13:54:38 -0700870def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -0700871 """Make a temp file and add it to the list of things to be deleted
872 when Cleanup() is called. Return the filename."""
873 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
874 os.close(fd)
875 OPTIONS.tempfiles.append(fn)
876 return fn
877
878
Doug Zongkereef39442009-04-02 12:14:19 -0700879def Cleanup():
880 for i in OPTIONS.tempfiles:
881 if os.path.isdir(i):
882 shutil.rmtree(i)
883 else:
884 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700885
886
887class PasswordManager(object):
888 def __init__(self):
889 self.editor = os.getenv("EDITOR", None)
890 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
891
892 def GetPasswords(self, items):
893 """Get passwords corresponding to each string in 'items',
894 returning a dict. (The dict may have keys in addition to the
895 values in 'items'.)
896
897 Uses the passwords in $ANDROID_PW_FILE if available, letting the
898 user edit that file to add more needed passwords. If no editor is
899 available, or $ANDROID_PW_FILE isn't define, prompts the user
900 interactively in the ordinary way.
901 """
902
903 current = self.ReadFile()
904
905 first = True
906 while True:
907 missing = []
908 for i in items:
909 if i not in current or not current[i]:
910 missing.append(i)
911 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -0700912 if not missing:
913 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -0700914
915 for i in missing:
916 current[i] = ""
917
918 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800919 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700920 answer = raw_input("try to edit again? [y]> ").strip()
921 if answer and answer[0] not in 'yY':
922 raise RuntimeError("key passwords unavailable")
923 first = False
924
925 current = self.UpdateAndReadFile(current)
926
Dan Albert8b72aef2015-03-23 19:13:21 -0700927 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -0700928 """Prompt the user to enter a value (password) for each key in
929 'current' whose value is fales. Returns a new dict with all the
930 values.
931 """
932 result = {}
933 for k, v in sorted(current.iteritems()):
934 if v:
935 result[k] = v
936 else:
937 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -0700938 result[k] = getpass.getpass(
939 "Enter password for %s key> " % k).strip()
940 if result[k]:
941 break
Doug Zongker8ce7c252009-05-22 13:34:54 -0700942 return result
943
944 def UpdateAndReadFile(self, current):
945 if not self.editor or not self.pwfile:
946 return self.PromptResult(current)
947
948 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -0700949 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700950 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
951 f.write("# (Additional spaces are harmless.)\n\n")
952
953 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700954 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
955 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -0700956 f.write("[[[ %s ]]] %s\n" % (v, k))
957 if not v and first_line is None:
958 # position cursor on first line with no password.
959 first_line = i + 4
960 f.close()
961
962 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
963 _, _ = p.communicate()
964
965 return self.ReadFile()
966
967 def ReadFile(self):
968 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700969 if self.pwfile is None:
970 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -0700971 try:
972 f = open(self.pwfile, "r")
973 for line in f:
974 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700975 if not line or line[0] == '#':
976 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -0700977 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
978 if not m:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800979 print("failed to parse password file: ", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700980 else:
981 result[m.group(2)] = m.group(1)
982 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -0700983 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700984 if e.errno != errno.ENOENT:
Tao Bao89fbb0f2017-01-10 10:47:58 -0800985 print("error reading password file: ", str(e))
Doug Zongker8ce7c252009-05-22 13:34:54 -0700986 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700987
988
Dan Albert8e0178d2015-01-27 15:53:15 -0800989def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
990 compress_type=None):
991 import datetime
992
993 # http://b/18015246
994 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
995 # for files larger than 2GiB. We can work around this by adjusting their
996 # limit. Note that `zipfile.writestr()` will not work for strings larger than
997 # 2GiB. The Python interpreter sometimes rejects strings that large (though
998 # it isn't clear to me exactly what circumstances cause this).
999 # `zipfile.write()` must be used directly to work around this.
1000 #
1001 # This mess can be avoided if we port to python3.
1002 saved_zip64_limit = zipfile.ZIP64_LIMIT
1003 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1004
1005 if compress_type is None:
1006 compress_type = zip_file.compression
1007 if arcname is None:
1008 arcname = filename
1009
1010 saved_stat = os.stat(filename)
1011
1012 try:
1013 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1014 # file to be zipped and reset it when we're done.
1015 os.chmod(filename, perms)
1016
1017 # Use a fixed timestamp so the output is repeatable.
1018 epoch = datetime.datetime.fromtimestamp(0)
1019 timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
1020 os.utime(filename, (timestamp, timestamp))
1021
1022 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1023 finally:
1024 os.chmod(filename, saved_stat.st_mode)
1025 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1026 zipfile.ZIP64_LIMIT = saved_zip64_limit
1027
1028
Tao Bao58c1b962015-05-20 09:32:18 -07001029def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001030 compress_type=None):
1031 """Wrap zipfile.writestr() function to work around the zip64 limit.
1032
1033 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1034 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1035 when calling crc32(bytes).
1036
1037 But it still works fine to write a shorter string into a large zip file.
1038 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1039 when we know the string won't be too long.
1040 """
1041
1042 saved_zip64_limit = zipfile.ZIP64_LIMIT
1043 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1044
1045 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1046 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001047 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001048 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001049 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001050 else:
Tao Baof3282b42015-04-01 11:21:55 -07001051 zinfo = zinfo_or_arcname
1052
1053 # If compress_type is given, it overrides the value in zinfo.
1054 if compress_type is not None:
1055 zinfo.compress_type = compress_type
1056
Tao Bao58c1b962015-05-20 09:32:18 -07001057 # If perms is given, it has a priority.
1058 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001059 # If perms doesn't set the file type, mark it as a regular file.
1060 if perms & 0o770000 == 0:
1061 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001062 zinfo.external_attr = perms << 16
1063
Tao Baof3282b42015-04-01 11:21:55 -07001064 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001065 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1066
Dan Albert8b72aef2015-03-23 19:13:21 -07001067 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001068 zipfile.ZIP64_LIMIT = saved_zip64_limit
1069
1070
1071def ZipClose(zip_file):
1072 # http://b/18015246
1073 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1074 # central directory.
1075 saved_zip64_limit = zipfile.ZIP64_LIMIT
1076 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1077
1078 zip_file.close()
1079
1080 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001081
1082
1083class DeviceSpecificParams(object):
1084 module = None
1085 def __init__(self, **kwargs):
1086 """Keyword arguments to the constructor become attributes of this
1087 object, which is passed to all functions in the device-specific
1088 module."""
1089 for k, v in kwargs.iteritems():
1090 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001091 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001092
1093 if self.module is None:
1094 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001095 if not path:
1096 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001097 try:
1098 if os.path.isdir(path):
1099 info = imp.find_module("releasetools", [path])
1100 else:
1101 d, f = os.path.split(path)
1102 b, x = os.path.splitext(f)
1103 if x == ".py":
1104 f = b
1105 info = imp.find_module(f, [d])
Tao Bao89fbb0f2017-01-10 10:47:58 -08001106 print("loaded device-specific extensions from", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001107 self.module = imp.load_module("device_specific", *info)
1108 except ImportError:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001109 print("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001110
1111 def _DoCall(self, function_name, *args, **kwargs):
1112 """Call the named function in the device-specific module, passing
1113 the given args and kwargs. The first argument to the call will be
1114 the DeviceSpecific object itself. If there is no module, or the
1115 module does not define the function, return the value of the
1116 'default' kwarg (which itself defaults to None)."""
1117 if self.module is None or not hasattr(self.module, function_name):
1118 return kwargs.get("default", None)
1119 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1120
1121 def FullOTA_Assertions(self):
1122 """Called after emitting the block of assertions at the top of a
1123 full OTA package. Implementations can add whatever additional
1124 assertions they like."""
1125 return self._DoCall("FullOTA_Assertions")
1126
Doug Zongkere5ff5902012-01-17 10:55:37 -08001127 def FullOTA_InstallBegin(self):
1128 """Called at the start of full OTA installation."""
1129 return self._DoCall("FullOTA_InstallBegin")
1130
Doug Zongker05d3dea2009-06-22 11:32:31 -07001131 def FullOTA_InstallEnd(self):
1132 """Called at the end of full OTA installation; typically this is
1133 used to install the image for the device's baseband processor."""
1134 return self._DoCall("FullOTA_InstallEnd")
1135
1136 def IncrementalOTA_Assertions(self):
1137 """Called after emitting the block of assertions at the top of an
1138 incremental OTA package. Implementations can add whatever
1139 additional assertions they like."""
1140 return self._DoCall("IncrementalOTA_Assertions")
1141
Doug Zongkere5ff5902012-01-17 10:55:37 -08001142 def IncrementalOTA_VerifyBegin(self):
1143 """Called at the start of the verification phase of incremental
1144 OTA installation; additional checks can be placed here to abort
1145 the script before any changes are made."""
1146 return self._DoCall("IncrementalOTA_VerifyBegin")
1147
Doug Zongker05d3dea2009-06-22 11:32:31 -07001148 def IncrementalOTA_VerifyEnd(self):
1149 """Called at the end of the verification phase of incremental OTA
1150 installation; additional checks can be placed here to abort the
1151 script before any changes are made."""
1152 return self._DoCall("IncrementalOTA_VerifyEnd")
1153
Doug Zongkere5ff5902012-01-17 10:55:37 -08001154 def IncrementalOTA_InstallBegin(self):
1155 """Called at the start of incremental OTA installation (after
1156 verification is complete)."""
1157 return self._DoCall("IncrementalOTA_InstallBegin")
1158
Doug Zongker05d3dea2009-06-22 11:32:31 -07001159 def IncrementalOTA_InstallEnd(self):
1160 """Called at the end of incremental OTA installation; typically
1161 this is used to install the image for the device's baseband
1162 processor."""
1163 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001164
Tao Bao9bc6bb22015-11-09 16:58:28 -08001165 def VerifyOTA_Assertions(self):
1166 return self._DoCall("VerifyOTA_Assertions")
1167
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001168class File(object):
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001169 def __init__(self, name, data, compress_size = None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001170 self.name = name
1171 self.data = data
1172 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001173 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001174 self.sha1 = sha1(data).hexdigest()
1175
1176 @classmethod
1177 def FromLocalFile(cls, name, diskname):
1178 f = open(diskname, "rb")
1179 data = f.read()
1180 f.close()
1181 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001182
1183 def WriteToTemp(self):
1184 t = tempfile.NamedTemporaryFile()
1185 t.write(self.data)
1186 t.flush()
1187 return t
1188
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001189 def WriteToDir(self, d):
1190 with open(os.path.join(d, self.name), "wb") as fp:
1191 fp.write(self.data)
1192
Geremy Condra36bd3652014-02-06 19:45:10 -08001193 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001194 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001195
1196DIFF_PROGRAM_BY_EXT = {
1197 ".gz" : "imgdiff",
1198 ".zip" : ["imgdiff", "-z"],
1199 ".jar" : ["imgdiff", "-z"],
1200 ".apk" : ["imgdiff", "-z"],
1201 ".img" : "imgdiff",
1202 }
1203
1204class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001205 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001206 self.tf = tf
1207 self.sf = sf
1208 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001209 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001210
1211 def ComputePatch(self):
1212 """Compute the patch (as a string of data) needed to turn sf into
1213 tf. Returns the same tuple as GetPatch()."""
1214
1215 tf = self.tf
1216 sf = self.sf
1217
Doug Zongker24cd2802012-08-14 16:36:15 -07001218 if self.diff_program:
1219 diff_program = self.diff_program
1220 else:
1221 ext = os.path.splitext(tf.name)[1]
1222 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001223
1224 ttemp = tf.WriteToTemp()
1225 stemp = sf.WriteToTemp()
1226
1227 ext = os.path.splitext(tf.name)[1]
1228
1229 try:
1230 ptemp = tempfile.NamedTemporaryFile()
1231 if isinstance(diff_program, list):
1232 cmd = copy.copy(diff_program)
1233 else:
1234 cmd = [diff_program]
1235 cmd.append(stemp.name)
1236 cmd.append(ttemp.name)
1237 cmd.append(ptemp.name)
1238 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001239 err = []
1240 def run():
1241 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001242 if e:
1243 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001244 th = threading.Thread(target=run)
1245 th.start()
1246 th.join(timeout=300) # 5 mins
1247 if th.is_alive():
Tao Bao89fbb0f2017-01-10 10:47:58 -08001248 print("WARNING: diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001249 p.terminate()
1250 th.join(5)
1251 if th.is_alive():
1252 p.kill()
1253 th.join()
1254
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001255 if err or p.returncode != 0:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001256 print("WARNING: failure running %s:\n%s\n" % (
1257 diff_program, "".join(err)))
Doug Zongkerf8340082014-08-05 10:39:37 -07001258 self.patch = None
1259 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001260 diff = ptemp.read()
1261 finally:
1262 ptemp.close()
1263 stemp.close()
1264 ttemp.close()
1265
1266 self.patch = diff
1267 return self.tf, self.sf, self.patch
1268
1269
1270 def GetPatch(self):
1271 """Return a tuple (target_file, source_file, patch_data).
1272 patch_data may be None if ComputePatch hasn't been called, or if
1273 computing the patch failed."""
1274 return self.tf, self.sf, self.patch
1275
1276
1277def ComputeDifferences(diffs):
1278 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao89fbb0f2017-01-10 10:47:58 -08001279 print(len(diffs), "diffs to compute")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001280
1281 # Do the largest files first, to try and reduce the long-pole effect.
1282 by_size = [(i.tf.size, i) for i in diffs]
1283 by_size.sort(reverse=True)
1284 by_size = [i[1] for i in by_size]
1285
1286 lock = threading.Lock()
1287 diff_iter = iter(by_size) # accessed under lock
1288
1289 def worker():
1290 try:
1291 lock.acquire()
1292 for d in diff_iter:
1293 lock.release()
1294 start = time.time()
1295 d.ComputePatch()
1296 dur = time.time() - start
1297 lock.acquire()
1298
1299 tf, sf, patch = d.GetPatch()
1300 if sf.name == tf.name:
1301 name = tf.name
1302 else:
1303 name = "%s (%s)" % (tf.name, sf.name)
1304 if patch is None:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001305 print("patching failed! %s" % (name,))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001306 else:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001307 print("%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
1308 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001309 lock.release()
Dan Albert8b72aef2015-03-23 19:13:21 -07001310 except Exception as e:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001311 print(e)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001312 raise
1313
1314 # start worker threads; wait for them all to finish.
1315 threads = [threading.Thread(target=worker)
1316 for i in range(OPTIONS.worker_threads)]
1317 for th in threads:
1318 th.start()
1319 while threads:
1320 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001321
1322
Dan Albert8b72aef2015-03-23 19:13:21 -07001323class BlockDifference(object):
1324 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001325 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001326 self.tgt = tgt
1327 self.src = src
1328 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001329 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001330 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001331
Tao Baodd2a5892015-03-12 12:32:37 -07001332 if version is None:
1333 version = 1
1334 if OPTIONS.info_dict:
1335 version = max(
1336 int(i) for i in
1337 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001338 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001339 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001340
1341 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001342 version=self.version,
1343 disable_imgdiff=self.disable_imgdiff)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001344 tmpdir = tempfile.mkdtemp()
1345 OPTIONS.tempfiles.append(tmpdir)
1346 self.path = os.path.join(tmpdir, partition)
1347 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001348 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001349 self.touched_src_ranges = b.touched_src_ranges
1350 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001351
Tao Baoaac4ad52015-10-16 15:26:34 -07001352 if src is None:
1353 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1354 else:
1355 _, self.device = GetTypeAndDevice("/" + partition,
1356 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001357
Tao Baod8d14be2016-02-04 14:26:02 -08001358 @property
1359 def required_cache(self):
1360 return self._required_cache
1361
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001362 def WriteScript(self, script, output_zip, progress=None):
1363 if not self.src:
1364 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001365 script.Print("Patching %s image unconditionally..." % (self.partition,))
1366 else:
1367 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001368
Dan Albert8b72aef2015-03-23 19:13:21 -07001369 if progress:
1370 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001371 self._WriteUpdate(script, output_zip)
Tianjie Xub2deb222016-03-25 15:01:33 -07001372 if OPTIONS.verify:
1373 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001374
Tao Bao9bc6bb22015-11-09 16:58:28 -08001375 def WriteStrictVerifyScript(self, script):
1376 """Verify all the blocks in the care_map, including clobbered blocks.
1377
1378 This differs from the WriteVerifyScript() function: a) it prints different
1379 error messages; b) it doesn't allow half-way updated images to pass the
1380 verification."""
1381
1382 partition = self.partition
1383 script.Print("Verifying %s..." % (partition,))
1384 ranges = self.tgt.care_map
1385 ranges_str = ranges.to_string_raw()
1386 script.AppendExtra('range_sha1("%s", "%s") == "%s" && '
1387 'ui_print(" Verified.") || '
1388 'ui_print("\\"%s\\" has unexpected contents.");' % (
1389 self.device, ranges_str,
1390 self.tgt.TotalSha1(include_clobbered_blocks=True),
1391 self.device))
1392 script.AppendExtra("")
1393
Tao Baod522bdc2016-04-12 15:53:16 -07001394 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001395 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001396
1397 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001398 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001399 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001400
1401 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001402 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001403 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001404 ranges = self.touched_src_ranges
1405 expected_sha1 = self.touched_src_sha1
1406 else:
1407 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1408 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001409
1410 # No blocks to be checked, skipping.
1411 if not ranges:
1412 return
1413
Tao Bao5ece99d2015-05-12 11:42:31 -07001414 ranges_str = ranges.to_string_raw()
Tao Bao8fad03e2017-03-01 14:36:26 -08001415 script.AppendExtra(('if (range_sha1("%s", "%s") == "%s" || '
1416 'block_image_verify("%s", '
1417 'package_extract_file("%s.transfer.list"), '
1418 '"%s.new.dat", "%s.patch.dat")) then') % (
1419 self.device, ranges_str, expected_sha1,
1420 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001421 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001422 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001423
Tianjie Xufc3422a2015-12-15 11:53:59 -08001424 if self.version >= 4:
1425
1426 # Bug: 21124327
1427 # When generating incrementals for the system and vendor partitions in
1428 # version 4 or newer, explicitly check the first block (which contains
1429 # the superblock) of the partition to see if it's what we expect. If
1430 # this check fails, give an explicit log message about the partition
1431 # having been remounted R/W (the most likely explanation).
1432 if self.check_first_block:
1433 script.AppendExtra('check_first_block("%s");' % (self.device,))
1434
1435 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001436 if partition == "system":
1437 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1438 else:
1439 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001440 script.AppendExtra((
1441 'ifelse (block_image_recover("{device}", "{ranges}") && '
1442 'block_image_verify("{device}", '
1443 'package_extract_file("{partition}.transfer.list"), '
1444 '"{partition}.new.dat", "{partition}.patch.dat"), '
1445 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001446 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001447 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001448 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001449
Tao Baodd2a5892015-03-12 12:32:37 -07001450 # Abort the OTA update. Note that the incremental OTA cannot be applied
1451 # even if it may match the checksum of the target partition.
1452 # a) If version < 3, operations like move and erase will make changes
1453 # unconditionally and damage the partition.
1454 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001455 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001456 if partition == "system":
1457 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1458 else:
1459 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1460 script.AppendExtra((
1461 'abort("E%d: %s partition has unexpected contents");\n'
1462 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001463
Tao Bao5fcaaef2015-06-01 13:40:49 -07001464 def _WritePostInstallVerifyScript(self, script):
1465 partition = self.partition
1466 script.Print('Verifying the updated %s image...' % (partition,))
1467 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1468 ranges = self.tgt.care_map
1469 ranges_str = ranges.to_string_raw()
1470 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1471 self.device, ranges_str,
1472 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001473
1474 # Bug: 20881595
1475 # Verify that extended blocks are really zeroed out.
1476 if self.tgt.extended:
1477 ranges_str = self.tgt.extended.to_string_raw()
1478 script.AppendExtra('if range_sha1("%s", "%s") == "%s" then' % (
1479 self.device, ranges_str,
1480 self._HashZeroBlocks(self.tgt.extended.size())))
1481 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001482 if partition == "system":
1483 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1484 else:
1485 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001486 script.AppendExtra(
1487 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001488 ' abort("E%d: %s partition has unexpected non-zero contents after '
1489 'OTA update");\n'
1490 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001491 else:
1492 script.Print('Verified the updated %s image.' % (partition,))
1493
Tianjie Xu209db462016-05-24 17:34:52 -07001494 if partition == "system":
1495 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1496 else:
1497 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1498
Tao Bao5fcaaef2015-06-01 13:40:49 -07001499 script.AppendExtra(
1500 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001501 ' abort("E%d: %s partition has unexpected contents after OTA '
1502 'update");\n'
1503 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001504
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001505 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001506 ZipWrite(output_zip,
1507 '{}.transfer.list'.format(self.path),
1508 '{}.transfer.list'.format(self.partition))
1509 ZipWrite(output_zip,
1510 '{}.new.dat'.format(self.path),
1511 '{}.new.dat'.format(self.partition))
1512 ZipWrite(output_zip,
1513 '{}.patch.dat'.format(self.path),
1514 '{}.patch.dat'.format(self.partition),
1515 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001516
Tianjie Xu209db462016-05-24 17:34:52 -07001517 if self.partition == "system":
1518 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1519 else:
1520 code = ErrorCode.VENDOR_UPDATE_FAILURE
1521
Dan Albert8e0178d2015-01-27 15:53:15 -08001522 call = ('block_image_update("{device}", '
1523 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub2deb222016-03-25 15:01:33 -07001524 '"{partition}.new.dat", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001525 ' abort("E{code}: Failed to update {partition} image.");'.format(
1526 device=self.device, partition=self.partition, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001527 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001528
Dan Albert8b72aef2015-03-23 19:13:21 -07001529 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001530 data = source.ReadRangeSet(ranges)
1531 ctx = sha1()
1532
1533 for p in data:
1534 ctx.update(p)
1535
1536 return ctx.hexdigest()
1537
Tao Baoe9b61912015-07-09 17:37:49 -07001538 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1539 """Return the hash value for all zero blocks."""
1540 zero_block = '\x00' * 4096
1541 ctx = sha1()
1542 for _ in range(num_blocks):
1543 ctx.update(zero_block)
1544
1545 return ctx.hexdigest()
1546
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001547
1548DataImage = blockimgdiff.DataImage
1549
Doug Zongker96a57e72010-09-26 14:57:41 -07001550# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001551PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001552 "ext4": "EMMC",
1553 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001554 "f2fs": "EMMC",
1555 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001556}
Doug Zongker96a57e72010-09-26 14:57:41 -07001557
1558def GetTypeAndDevice(mount_point, info):
1559 fstab = info["fstab"]
1560 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001561 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1562 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001563 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001564 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001565
1566
1567def ParseCertificate(data):
1568 """Parse a PEM-format certificate."""
1569 cert = []
1570 save = False
1571 for line in data.split("\n"):
1572 if "--END CERTIFICATE--" in line:
1573 break
1574 if save:
1575 cert.append(line)
1576 if "--BEGIN CERTIFICATE--" in line:
1577 save = True
1578 cert = "".join(cert).decode('base64')
1579 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08001580
Doug Zongker412c02f2014-02-13 10:58:24 -08001581def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
1582 info_dict=None):
Doug Zongkerc9253822014-02-04 12:17:58 -08001583 """Generate a binary patch that creates the recovery image starting
1584 with the boot image. (Most of the space in these images is just the
1585 kernel, which is identical for the two, so the resulting patch
1586 should be efficient.) Add it to the output zip, along with a shell
1587 script that is run from init.rc on first boot to actually do the
1588 patching and install the new recovery image.
1589
1590 recovery_img and boot_img should be File objects for the
1591 corresponding images. info should be the dictionary returned by
1592 common.LoadInfoDict() on the input target_files.
1593 """
1594
Doug Zongker412c02f2014-02-13 10:58:24 -08001595 if info_dict is None:
1596 info_dict = OPTIONS.info_dict
1597
Tao Baof2cffbd2015-07-22 12:33:18 -07001598 full_recovery_image = info_dict.get("full_recovery_image", None) == "true"
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001599 system_root_image = info_dict.get("system_root_image", None) == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08001600
Tao Baof2cffbd2015-07-22 12:33:18 -07001601 if full_recovery_image:
1602 output_sink("etc/recovery.img", recovery_img.data)
1603
1604 else:
1605 diff_program = ["imgdiff"]
1606 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
1607 if os.path.exists(path):
1608 diff_program.append("-b")
1609 diff_program.append(path)
1610 bonus_args = "-b /system/etc/recovery-resource.dat"
1611 else:
1612 bonus_args = ""
1613
1614 d = Difference(recovery_img, boot_img, diff_program=diff_program)
1615 _, _, patch = d.ComputePatch()
1616 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08001617
Dan Albertebb19aa2015-03-27 19:11:53 -07001618 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07001619 # The following GetTypeAndDevice()s need to use the path in the target
1620 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07001621 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
1622 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
1623 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07001624 return
Doug Zongkerc9253822014-02-04 12:17:58 -08001625
Tao Baof2cffbd2015-07-22 12:33:18 -07001626 if full_recovery_image:
1627 sh = """#!/system/bin/sh
1628if ! applypatch -c %(type)s:%(device)s:%(size)d:%(sha1)s; then
1629 applypatch /system/etc/recovery.img %(type)s:%(device)s %(sha1)s %(size)d && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed"
1630else
1631 log -t recovery "Recovery image already installed"
1632fi
1633""" % {'type': recovery_type,
1634 'device': recovery_device,
1635 'sha1': recovery_img.sha1,
1636 'size': recovery_img.size}
1637 else:
1638 sh = """#!/system/bin/sh
Doug Zongkerc9253822014-02-04 12:17:58 -08001639if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
1640 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"
1641else
1642 log -t recovery "Recovery image already installed"
1643fi
Dan Albert8b72aef2015-03-23 19:13:21 -07001644""" % {'boot_size': boot_img.size,
1645 'boot_sha1': boot_img.sha1,
1646 'recovery_size': recovery_img.size,
1647 'recovery_sha1': recovery_img.sha1,
1648 'boot_type': boot_type,
1649 'boot_device': boot_device,
1650 'recovery_type': recovery_type,
1651 'recovery_device': recovery_device,
1652 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08001653
1654 # The install script location moved from /system/etc to /system/bin
Tao Bao9f0c8df2015-07-07 18:31:47 -07001655 # in the L release. Parse init.*.rc files to find out where the
Doug Zongkerc9253822014-02-04 12:17:58 -08001656 # target-files expects it to be, and put it there.
1657 sh_location = "etc/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07001658 found = False
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001659 if system_root_image:
1660 init_rc_dir = os.path.join(input_dir, "ROOT")
1661 else:
1662 init_rc_dir = os.path.join(input_dir, "BOOT", "RAMDISK")
Tao Bao9f0c8df2015-07-07 18:31:47 -07001663 init_rc_files = os.listdir(init_rc_dir)
1664 for init_rc_file in init_rc_files:
1665 if (not init_rc_file.startswith('init.') or
1666 not init_rc_file.endswith('.rc')):
1667 continue
1668
1669 with open(os.path.join(init_rc_dir, init_rc_file)) as f:
Doug Zongkerc9253822014-02-04 12:17:58 -08001670 for line in f:
Dan Albert8b72aef2015-03-23 19:13:21 -07001671 m = re.match(r"^service flash_recovery /system/(\S+)\s*$", line)
Doug Zongkerc9253822014-02-04 12:17:58 -08001672 if m:
1673 sh_location = m.group(1)
Tao Bao9f0c8df2015-07-07 18:31:47 -07001674 found = True
Doug Zongkerc9253822014-02-04 12:17:58 -08001675 break
Tao Bao9f0c8df2015-07-07 18:31:47 -07001676
1677 if found:
1678 break
1679
Tao Bao89fbb0f2017-01-10 10:47:58 -08001680 print("putting script in", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08001681
1682 output_sink(sh_location, sh)