blob: 2b043344ae2fdfc92ddb4d62014f2f7b6b882db5 [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
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070052 # Set up search path, in order to find framework/ and lib64/. At the time of
53 # running this function, user-supplied search path (`--path`) hasn't been
54 # available. So the value set here is the default, which might be overridden
55 # by commandline flag later.
56 exec_path = sys.argv[0]
57 if exec_path.endswith('.py'):
58 script_name = os.path.basename(exec_path)
59 # logger hasn't been initialized yet at this point. Use print to output
60 # warnings.
61 print(
62 'Warning: releasetools script should be invoked as hermetic Python '
63 'executable -- build and run `{}` directly.'.format(script_name[:-3]),
64 file=sys.stderr)
Robin Lee34ea7392020-01-02 20:21:18 +010065 self.search_path = os.path.realpath(os.path.join(os.path.dirname(exec_path), '..'))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080068 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.extra_signapk_args = []
70 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080071 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080072 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070073 self.public_key_suffix = ".x509.pem"
74 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070075 # use otatools built boot_signer by default
76 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070077 self.boot_signer_args = []
78 self.verity_signer_path = None
79 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070080 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080081 self.aftl_server = None
82 self.aftl_key_path = None
83 self.aftl_manufacturer_key_path = None
84 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.verbose = False
86 self.tempfiles = []
87 self.device_specific = None
88 self.extras = {}
89 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070090 self.source_info_dict = None
91 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070092 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070093 # Stash size cannot exceed cache_size * threshold.
94 self.cache_size = None
95 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070096 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070097
98
99OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700100
Tao Bao71197512018-10-11 14:08:45 -0700101# The block size that's used across the releasetools scripts.
102BLOCK_SIZE = 4096
103
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800104# Values for "certificate" in apkcerts that mean special things.
105SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
106
Tao Bao5cc0abb2019-03-21 10:18:05 -0700107# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
108# that system_other is not in the list because we don't want to include its
109# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900110AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700111 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800112
Tao Bao08c190f2019-06-03 23:07:58 -0700113# Chained VBMeta partitions.
114AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
115
Tianjie Xu861f4132018-09-12 11:49:33 -0700116# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900117PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700118
119
Tianjie Xu209db462016-05-24 17:34:52 -0700120class ErrorCode(object):
121 """Define error_codes for failures that happen during the actual
122 update package installation.
123
124 Error codes 0-999 are reserved for failures before the package
125 installation (i.e. low battery, package verification failure).
126 Detailed code in 'bootable/recovery/error_code.h' """
127
128 SYSTEM_VERIFICATION_FAILURE = 1000
129 SYSTEM_UPDATE_FAILURE = 1001
130 SYSTEM_UNEXPECTED_CONTENTS = 1002
131 SYSTEM_NONZERO_CONTENTS = 1003
132 SYSTEM_RECOVER_FAILURE = 1004
133 VENDOR_VERIFICATION_FAILURE = 2000
134 VENDOR_UPDATE_FAILURE = 2001
135 VENDOR_UNEXPECTED_CONTENTS = 2002
136 VENDOR_NONZERO_CONTENTS = 2003
137 VENDOR_RECOVER_FAILURE = 2004
138 OEM_PROP_MISMATCH = 3000
139 FINGERPRINT_MISMATCH = 3001
140 THUMBPRINT_MISMATCH = 3002
141 OLDER_BUILD = 3003
142 DEVICE_MISMATCH = 3004
143 BAD_PATCH_FILE = 3005
144 INSUFFICIENT_CACHE_SPACE = 3006
145 TUNE_PARTITION_FAILURE = 3007
146 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800147
Tao Bao80921982018-03-21 21:02:19 -0700148
Dan Albert8b72aef2015-03-23 19:13:21 -0700149class ExternalError(RuntimeError):
150 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700151
152
Tao Bao32fcdab2018-10-12 10:30:39 -0700153def InitLogging():
154 DEFAULT_LOGGING_CONFIG = {
155 'version': 1,
156 'disable_existing_loggers': False,
157 'formatters': {
158 'standard': {
159 'format':
160 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
161 'datefmt': '%Y-%m-%d %H:%M:%S',
162 },
163 },
164 'handlers': {
165 'default': {
166 'class': 'logging.StreamHandler',
167 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700168 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700169 },
170 },
171 'loggers': {
172 '': {
173 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700175 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700176 }
177 }
178 }
179 env_config = os.getenv('LOGGING_CONFIG')
180 if env_config:
181 with open(env_config) as f:
182 config = json.load(f)
183 else:
184 config = DEFAULT_LOGGING_CONFIG
185
186 # Increase the logging level for verbose mode.
187 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700188 config = copy.deepcopy(config)
189 config['handlers']['default']['level'] = 'INFO'
190
191 if OPTIONS.logfile:
192 config = copy.deepcopy(config)
193 config['handlers']['logfile'] = {
194 'class': 'logging.FileHandler',
195 'formatter': 'standard',
196 'level': 'INFO',
197 'mode': 'w',
198 'filename': OPTIONS.logfile,
199 }
200 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700201
202 logging.config.dictConfig(config)
203
204
Tao Bao39451582017-05-04 11:10:47 -0700205def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700207
Tao Bao73dd4f42018-10-04 16:25:33 -0700208 Args:
209 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700210 verbose: Whether the commands should be shown. Default to the global
211 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700212 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
213 stdin, etc. stdout and stderr will default to subprocess.PIPE and
214 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800215 universal_newlines will default to True, as most of the users in
216 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700217
218 Returns:
219 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700220 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700221 if 'stdout' not in kwargs and 'stderr' not in kwargs:
222 kwargs['stdout'] = subprocess.PIPE
223 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800224 if 'universal_newlines' not in kwargs:
225 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 # Don't log any if caller explicitly says so.
227 if verbose != False:
228 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700229 return subprocess.Popen(args, **kwargs)
230
231
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800233 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800234
235 Args:
236 args: The command represented as a list of strings.
237 verbose: Whether the commands should be shown. Default to the global
238 verbosity if unspecified.
239 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
240 stdin, etc. stdout and stderr will default to subprocess.PIPE and
241 subprocess.STDOUT respectively unless caller specifies any of them.
242
Bill Peckham889b0c62019-02-21 18:53:37 -0800243 Raises:
244 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800245 """
246 proc = Run(args, verbose=verbose, **kwargs)
247 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800248
249 if proc.returncode != 0:
250 raise ExternalError(
251 "Failed to run command '{}' (exit code {})".format(
252 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800253
254
Tao Bao986ee862018-10-04 15:46:16 -0700255def RunAndCheckOutput(args, verbose=None, **kwargs):
256 """Runs the given command and returns the output.
257
258 Args:
259 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700260 verbose: Whether the commands should be shown. Default to the global
261 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700262 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
263 stdin, etc. stdout and stderr will default to subprocess.PIPE and
264 subprocess.STDOUT respectively unless caller specifies any of them.
265
266 Returns:
267 The output string.
268
269 Raises:
270 ExternalError: On non-zero exit from the command.
271 """
Tao Bao986ee862018-10-04 15:46:16 -0700272 proc = Run(args, verbose=verbose, **kwargs)
273 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800274 if output is None:
275 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 # Don't log any if caller explicitly says so.
277 if verbose != False:
278 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700279 if proc.returncode != 0:
280 raise ExternalError(
281 "Failed to run command '{}' (exit code {}):\n{}".format(
282 args, proc.returncode, output))
283 return output
284
285
Tao Baoc765cca2018-01-31 17:32:40 -0800286def RoundUpTo4K(value):
287 rounded_up = value + 4095
288 return rounded_up - (rounded_up % 4096)
289
290
Ying Wang7e6d4e42010-12-13 16:25:36 -0800291def CloseInheritedPipes():
292 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
293 before doing other work."""
294 if platform.system() != "Darwin":
295 return
296 for d in range(3, 1025):
297 try:
298 stat = os.fstat(d)
299 if stat is not None:
300 pipebit = stat[0] & 0x1000
301 if pipebit != 0:
302 os.close(d)
303 except OSError:
304 pass
305
306
Tao Bao1c320f82019-10-04 23:25:12 -0700307class BuildInfo(object):
308 """A class that holds the information for a given build.
309
310 This class wraps up the property querying for a given source or target build.
311 It abstracts away the logic of handling OEM-specific properties, and caches
312 the commonly used properties such as fingerprint.
313
314 There are two types of info dicts: a) build-time info dict, which is generated
315 at build time (i.e. included in a target_files zip); b) OEM info dict that is
316 specified at package generation time (via command line argument
317 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
318 having "oem_fingerprint_properties" in build-time info dict), all the queries
319 would be answered based on build-time info dict only. Otherwise if using
320 OEM-specific properties, some of them will be calculated from two info dicts.
321
322 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800323 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700324
325 Attributes:
326 info_dict: The build-time info dict.
327 is_ab: Whether it's a build that uses A/B OTA.
328 oem_dicts: A list of OEM dicts.
329 oem_props: A list of OEM properties that should be read from OEM dicts; None
330 if the build doesn't use any OEM-specific property.
331 fingerprint: The fingerprint of the build, which would be calculated based
332 on OEM properties if applicable.
333 device: The device name, which could come from OEM dicts if applicable.
334 """
335
336 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
337 "ro.product.manufacturer", "ro.product.model",
338 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700339 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
340 "product", "odm", "vendor", "system_ext", "system"]
341 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
342 "product", "product_services", "odm", "vendor", "system"]
343 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700344
Tao Bao3ed35d32019-10-07 20:48:48 -0700345 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700346 """Initializes a BuildInfo instance with the given dicts.
347
348 Note that it only wraps up the given dicts, without making copies.
349
350 Arguments:
351 info_dict: The build-time info dict.
352 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
353 that it always uses the first dict to calculate the fingerprint or the
354 device name. The rest would be used for asserting OEM properties only
355 (e.g. one package can be installed on one of these devices).
356
357 Raises:
358 ValueError: On invalid inputs.
359 """
360 self.info_dict = info_dict
361 self.oem_dicts = oem_dicts
362
363 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700364
Hongguang Chend7c160f2020-05-03 21:24:26 -0700365 # Skip _oem_props if oem_dicts is None to use BuildInfo in
366 # sign_target_files_apks
367 if self.oem_dicts:
368 self._oem_props = info_dict.get("oem_fingerprint_properties")
369 else:
370 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700371
Daniel Normand5fe8622020-01-08 17:01:11 -0800372 def check_fingerprint(fingerprint):
373 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
374 raise ValueError(
375 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
376 "3.2.2. Build Parameters.".format(fingerprint))
377
378
379 self._partition_fingerprints = {}
380 for partition in PARTITIONS_WITH_CARE_MAP:
381 try:
382 fingerprint = self.CalculatePartitionFingerprint(partition)
383 check_fingerprint(fingerprint)
384 self._partition_fingerprints[partition] = fingerprint
385 except ExternalError:
386 continue
387 if "system" in self._partition_fingerprints:
388 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
389 # need a fingerprint when creating the image.
390 self._partition_fingerprints[
391 "system_other"] = self._partition_fingerprints["system"]
392
Tao Bao1c320f82019-10-04 23:25:12 -0700393 # These two should be computed only after setting self._oem_props.
394 self._device = self.GetOemProperty("ro.product.device")
395 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700397
398 @property
399 def is_ab(self):
400 return self._is_ab
401
402 @property
403 def device(self):
404 return self._device
405
406 @property
407 def fingerprint(self):
408 return self._fingerprint
409
410 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700411 def oem_props(self):
412 return self._oem_props
413
414 def __getitem__(self, key):
415 return self.info_dict[key]
416
417 def __setitem__(self, key, value):
418 self.info_dict[key] = value
419
420 def get(self, key, default=None):
421 return self.info_dict.get(key, default)
422
423 def items(self):
424 return self.info_dict.items()
425
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000426 def _GetRawBuildProp(self, prop, partition):
427 prop_file = '{}.build.prop'.format(
428 partition) if partition else 'build.prop'
429 partition_props = self.info_dict.get(prop_file)
430 if not partition_props:
431 return None
432 return partition_props.GetProp(prop)
433
Daniel Normand5fe8622020-01-08 17:01:11 -0800434 def GetPartitionBuildProp(self, prop, partition):
435 """Returns the inquired build property for the provided partition."""
436 # If provided a partition for this property, only look within that
437 # partition's build.prop.
438 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
439 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
440 else:
441 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000442
443 prop_val = self._GetRawBuildProp(prop, partition)
444 if prop_val is not None:
445 return prop_val
446 raise ExternalError("couldn't find %s in %s.build.prop" %
447 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800448
Tao Bao1c320f82019-10-04 23:25:12 -0700449 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800450 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700451 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
452 return self._ResolveRoProductBuildProp(prop)
453
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000454 prop_val = self._GetRawBuildProp(prop, None)
455 if prop_val is not None:
456 return prop_val
457
458 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700459
460 def _ResolveRoProductBuildProp(self, prop):
461 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000462 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700463 if prop_val:
464 return prop_val
465
Steven Laver8e2086e2020-04-27 16:26:31 -0700466 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000467 source_order_val = self._GetRawBuildProp(
468 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700469 if source_order_val:
470 source_order = source_order_val.split(",")
471 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700472 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700473
474 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700475 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700476 raise ExternalError(
477 "Invalid ro.product.property_source_order '{}'".format(source_order))
478
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000479 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700480 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 "ro.product", "ro.product.{}".format(source_partition), 1)
482 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700483 if prop_val:
484 return prop_val
485
486 raise ExternalError("couldn't resolve {}".format(prop))
487
Steven Laver8e2086e2020-04-27 16:26:31 -0700488 def _GetRoProductPropsDefaultSourceOrder(self):
489 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
490 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700492 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000493 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700494 if android_version == "10":
495 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
496 # NOTE: float() conversion of android_version will have rounding error.
497 # We are checking for "9" or less, and using "< 10" is well outside of
498 # possible floating point rounding.
499 try:
500 android_version_val = float(android_version)
501 except ValueError:
502 android_version_val = 0
503 if android_version_val < 10:
504 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
506
Tao Bao1c320f82019-10-04 23:25:12 -0700507 def GetOemProperty(self, key):
508 if self.oem_props is not None and key in self.oem_props:
509 return self.oem_dicts[0][key]
510 return self.GetBuildProp(key)
511
Daniel Normand5fe8622020-01-08 17:01:11 -0800512 def GetPartitionFingerprint(self, partition):
513 return self._partition_fingerprints.get(partition, None)
514
515 def CalculatePartitionFingerprint(self, partition):
516 try:
517 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
518 except ExternalError:
519 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
520 self.GetPartitionBuildProp("ro.product.brand", partition),
521 self.GetPartitionBuildProp("ro.product.name", partition),
522 self.GetPartitionBuildProp("ro.product.device", partition),
523 self.GetPartitionBuildProp("ro.build.version.release", partition),
524 self.GetPartitionBuildProp("ro.build.id", partition),
525 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
526 self.GetPartitionBuildProp("ro.build.type", partition),
527 self.GetPartitionBuildProp("ro.build.tags", partition))
528
Tao Bao1c320f82019-10-04 23:25:12 -0700529 def CalculateFingerprint(self):
530 if self.oem_props is None:
531 try:
532 return self.GetBuildProp("ro.build.fingerprint")
533 except ExternalError:
534 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
535 self.GetBuildProp("ro.product.brand"),
536 self.GetBuildProp("ro.product.name"),
537 self.GetBuildProp("ro.product.device"),
538 self.GetBuildProp("ro.build.version.release"),
539 self.GetBuildProp("ro.build.id"),
540 self.GetBuildProp("ro.build.version.incremental"),
541 self.GetBuildProp("ro.build.type"),
542 self.GetBuildProp("ro.build.tags"))
543 return "%s/%s/%s:%s" % (
544 self.GetOemProperty("ro.product.brand"),
545 self.GetOemProperty("ro.product.name"),
546 self.GetOemProperty("ro.product.device"),
547 self.GetBuildProp("ro.build.thumbprint"))
548
549 def WriteMountOemScript(self, script):
550 assert self.oem_props is not None
551 recovery_mount_options = self.info_dict.get("recovery_mount_options")
552 script.Mount("/oem", recovery_mount_options)
553
554 def WriteDeviceAssertions(self, script, oem_no_mount):
555 # Read the property directly if not using OEM properties.
556 if not self.oem_props:
557 script.AssertDevice(self.device)
558 return
559
560 # Otherwise assert OEM properties.
561 if not self.oem_dicts:
562 raise ExternalError(
563 "No OEM file provided to answer expected assertions")
564
565 for prop in self.oem_props.split():
566 values = []
567 for oem_dict in self.oem_dicts:
568 if prop in oem_dict:
569 values.append(oem_dict[prop])
570 if not values:
571 raise ExternalError(
572 "The OEM file is missing the property %s" % (prop,))
573 script.AssertOemProperty(prop, values, oem_no_mount)
574
575
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000576def ReadFromInputFile(input_file, fn):
577 """Reads the contents of fn from input zipfile or directory."""
578 if isinstance(input_file, zipfile.ZipFile):
579 return input_file.read(fn).decode()
580 else:
581 path = os.path.join(input_file, *fn.split("/"))
582 try:
583 with open(path) as f:
584 return f.read()
585 except IOError as e:
586 if e.errno == errno.ENOENT:
587 raise KeyError(fn)
588
589
Tao Bao410ad8b2018-08-24 12:08:38 -0700590def LoadInfoDict(input_file, repacking=False):
591 """Loads the key/value pairs from the given input target_files.
592
593 It reads `META/misc_info.txt` file in the target_files input, does sanity
594 checks and returns the parsed key/value pairs for to the given build. It's
595 usually called early when working on input target_files files, e.g. when
596 generating OTAs, or signing builds. Note that the function may be called
597 against an old target_files file (i.e. from past dessert releases). So the
598 property parsing needs to be backward compatible.
599
600 In a `META/misc_info.txt`, a few properties are stored as links to the files
601 in the PRODUCT_OUT directory. It works fine with the build system. However,
602 they are no longer available when (re)generating images from target_files zip.
603 When `repacking` is True, redirect these properties to the actual files in the
604 unzipped directory.
605
606 Args:
607 input_file: The input target_files file, which could be an open
608 zipfile.ZipFile instance, or a str for the dir that contains the files
609 unzipped from a target_files file.
610 repacking: Whether it's trying repack an target_files file after loading the
611 info dict (default: False). If so, it will rewrite a few loaded
612 properties (e.g. selinux_fc, root_dir) to point to the actual files in
613 target_files file. When doing repacking, `input_file` must be a dir.
614
615 Returns:
616 A dict that contains the parsed key/value pairs.
617
618 Raises:
619 AssertionError: On invalid input arguments.
620 ValueError: On malformed input values.
621 """
622 if repacking:
623 assert isinstance(input_file, str), \
624 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700625
Doug Zongkerc9253822014-02-04 12:17:58 -0800626 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000627 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800628
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700629 try:
Michael Runge6e836112014-04-15 17:40:21 -0700630 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700631 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700632 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700633
Tao Bao410ad8b2018-08-24 12:08:38 -0700634 if "recovery_api_version" not in d:
635 raise ValueError("Failed to find 'recovery_api_version'")
636 if "fstab_version" not in d:
637 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800638
Tao Bao410ad8b2018-08-24 12:08:38 -0700639 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700640 # "selinux_fc" properties should point to the file_contexts files
641 # (file_contexts.bin) under META/.
642 for key in d:
643 if key.endswith("selinux_fc"):
644 fc_basename = os.path.basename(d[key])
645 fc_config = os.path.join(input_file, "META", fc_basename)
646 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700647
Daniel Norman72c626f2019-05-13 15:58:14 -0700648 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700649
Tom Cherryd14b8952018-08-09 14:26:00 -0700650 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700651 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700652 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700653 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700654
David Anderson0ec64ac2019-12-06 12:21:18 -0800655 # Redirect {partition}_base_fs_file for each of the named partitions.
656 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
657 key_name = part_name + "_base_fs_file"
658 if key_name not in d:
659 continue
660 basename = os.path.basename(d[key_name])
661 base_fs_file = os.path.join(input_file, "META", basename)
662 if os.path.exists(base_fs_file):
663 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700664 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700665 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800666 "Failed to find %s base fs file: %s", part_name, base_fs_file)
667 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700668
Doug Zongker37974732010-09-16 17:44:38 -0700669 def makeint(key):
670 if key in d:
671 d[key] = int(d[key], 0)
672
673 makeint("recovery_api_version")
674 makeint("blocksize")
675 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700676 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700677 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700678 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700679 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800680 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700681
Steve Muckle903a1ca2020-05-07 17:32:10 -0700682 boot_images = "boot.img"
683 if "boot_images" in d:
684 boot_images = d["boot_images"]
685 for b in boot_images.split():
686 makeint(b.replace(".img","_size"))
687
Tao Bao765668f2019-10-04 22:03:00 -0700688 # Load recovery fstab if applicable.
689 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800690
Tianjie Xu861f4132018-09-12 11:49:33 -0700691 # Tries to load the build props for all partitions with care_map, including
692 # system and vendor.
693 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800694 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000695 d[partition_prop] = PartitionBuildProps.FromInputFile(
696 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700697 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800698
Tao Bao3ed35d32019-10-07 20:48:48 -0700699 # Set up the salt (based on fingerprint) that will be used when adding AVB
700 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800701 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700702 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800703 for partition in PARTITIONS_WITH_CARE_MAP:
704 fingerprint = build_info.GetPartitionFingerprint(partition)
705 if fingerprint:
706 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800707
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700708 return d
709
Tao Baod1de6f32017-03-01 16:38:48 -0800710
Daniel Norman4cc9df62019-07-18 10:11:07 -0700711def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900712 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700713 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900714
Daniel Norman4cc9df62019-07-18 10:11:07 -0700715
716def LoadDictionaryFromFile(file_path):
717 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900718 return LoadDictionaryFromLines(lines)
719
720
Michael Runge6e836112014-04-15 17:40:21 -0700721def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700722 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700723 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700724 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700725 if not line or line.startswith("#"):
726 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700727 if "=" in line:
728 name, value = line.split("=", 1)
729 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700730 return d
731
Tao Baod1de6f32017-03-01 16:38:48 -0800732
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000733class PartitionBuildProps(object):
734 """The class holds the build prop of a particular partition.
735
736 This class loads the build.prop and holds the build properties for a given
737 partition. It also partially recognizes the 'import' statement in the
738 build.prop; and calculates alternative values of some specific build
739 properties during runtime.
740
741 Attributes:
742 input_file: a zipped target-file or an unzipped target-file directory.
743 partition: name of the partition.
744 props_allow_override: a list of build properties to search for the
745 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000746 build_props: a dict of build properties for the given partition.
747 prop_overrides: a set of props that are overridden by import.
748 placeholder_values: A dict of runtime variables' values to replace the
749 placeholders in the build.prop file. We expect exactly one value for
750 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000751 """
Tianjie Xu9afb2212020-05-10 21:48:15 +0000752 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000753 self.input_file = input_file
754 self.partition = name
755 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000756 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000757 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000758 self.prop_overrides = set()
759 self.placeholder_values = {}
760 if placeholder_values:
761 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000762
763 @staticmethod
764 def FromDictionary(name, build_props):
765 """Constructs an instance from a build prop dictionary."""
766
767 props = PartitionBuildProps("unknown", name)
768 props.build_props = build_props.copy()
769 return props
770
771 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000772 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000773 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000774 data = ''
775 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
776 '{}/build.prop'.format(name.upper())]:
777 try:
778 data = ReadFromInputFile(input_file, prop_file)
779 break
780 except KeyError:
781 logger.warning('Failed to read %s', prop_file)
782
Tianjie Xu9afb2212020-05-10 21:48:15 +0000783 props = PartitionBuildProps(input_file, name, placeholder_values)
784 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000785 return props
786
Tianjie Xu9afb2212020-05-10 21:48:15 +0000787 def _LoadBuildProp(self, data):
788 for line in data.split('\n'):
789 line = line.strip()
790 if not line or line.startswith("#"):
791 continue
792 if line.startswith("import"):
793 overrides = self._ImportParser(line)
794 duplicates = self.prop_overrides.intersection(overrides.keys())
795 if duplicates:
796 raise ValueError('prop {} is overridden multiple times'.format(
797 ','.join(duplicates)))
798 self.prop_overrides = self.prop_overrides.union(overrides.keys())
799 self.build_props.update(overrides)
800 elif "=" in line:
801 name, value = line.split("=", 1)
802 if name in self.prop_overrides:
803 raise ValueError('prop {} is set again after overridden by import '
804 'statement'.format(name))
805 self.build_props[name] = value
806
807 def _ImportParser(self, line):
808 """Parses the build prop in a given import statement."""
809
810 tokens = line.split()
Hongguang Chenb4702b72020-05-13 18:05:20 -0700811 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3) :
Tianjie Xu9afb2212020-05-10 21:48:15 +0000812 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700813
814 if len(tokens) == 3:
815 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
816 return {}
817
Tianjie Xu9afb2212020-05-10 21:48:15 +0000818 import_path = tokens[1]
819 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
820 raise ValueError('Unrecognized import path {}'.format(line))
821
822 # We only recognize a subset of import statement that the init process
823 # supports. And we can loose the restriction based on how the dynamic
824 # fingerprint is used in practice. The placeholder format should be
825 # ${placeholder}, and its value should be provided by the caller through
826 # the placeholder_values.
827 for prop, value in self.placeholder_values.items():
828 prop_place_holder = '${{{}}}'.format(prop)
829 if prop_place_holder in import_path:
830 import_path = import_path.replace(prop_place_holder, value)
831 if '$' in import_path:
832 logger.info('Unresolved place holder in import path %s', import_path)
833 return {}
834
835 import_path = import_path.replace('/{}'.format(self.partition),
836 self.partition.upper())
837 logger.info('Parsing build props override from %s', import_path)
838
839 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
840 d = LoadDictionaryFromLines(lines)
841 return {key: val for key, val in d.items()
842 if key in self.props_allow_override}
843
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000844 def GetProp(self, prop):
845 return self.build_props.get(prop)
846
847
Tianjie Xucfa86222016-03-07 16:31:19 -0800848def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
849 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700850 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800851 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 self.mount_point = mount_point
853 self.fs_type = fs_type
854 self.device = device
855 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700856 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700857
858 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800859 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700860 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700861 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700862 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700863
Tao Baod1de6f32017-03-01 16:38:48 -0800864 assert fstab_version == 2
865
866 d = {}
867 for line in data.split("\n"):
868 line = line.strip()
869 if not line or line.startswith("#"):
870 continue
871
872 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
873 pieces = line.split()
874 if len(pieces) != 5:
875 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
876
877 # Ignore entries that are managed by vold.
878 options = pieces[4]
879 if "voldmanaged=" in options:
880 continue
881
882 # It's a good line, parse it.
883 length = 0
884 options = options.split(",")
885 for i in options:
886 if i.startswith("length="):
887 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800888 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800889 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700890 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800891
Tao Baod1de6f32017-03-01 16:38:48 -0800892 mount_flags = pieces[3]
893 # Honor the SELinux context if present.
894 context = None
895 for i in mount_flags.split(","):
896 if i.startswith("context="):
897 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800898
Tao Baod1de6f32017-03-01 16:38:48 -0800899 mount_point = pieces[1]
900 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
901 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800902
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700903 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700904 # system. Other areas assume system is always at "/system" so point /system
905 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700906 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800907 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700908 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700909 return d
910
911
Tao Bao765668f2019-10-04 22:03:00 -0700912def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
913 """Finds the path to recovery fstab and loads its contents."""
914 # recovery fstab is only meaningful when installing an update via recovery
915 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
916 if info_dict.get('ab_update') == 'true':
917 return None
918
919 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
920 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
921 # cases, since it may load the info_dict from an old build (e.g. when
922 # generating incremental OTAs from that build).
923 system_root_image = info_dict.get('system_root_image') == 'true'
924 if info_dict.get('no_recovery') != 'true':
925 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
926 if isinstance(input_file, zipfile.ZipFile):
927 if recovery_fstab_path not in input_file.namelist():
928 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
929 else:
930 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
931 if not os.path.exists(path):
932 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
933 return LoadRecoveryFSTab(
934 read_helper, info_dict['fstab_version'], recovery_fstab_path,
935 system_root_image)
936
937 if info_dict.get('recovery_as_boot') == 'true':
938 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
939 if isinstance(input_file, zipfile.ZipFile):
940 if recovery_fstab_path not in input_file.namelist():
941 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
942 else:
943 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
944 if not os.path.exists(path):
945 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
946 return LoadRecoveryFSTab(
947 read_helper, info_dict['fstab_version'], recovery_fstab_path,
948 system_root_image)
949
950 return None
951
952
Doug Zongker37974732010-09-16 17:44:38 -0700953def DumpInfoDict(d):
954 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700955 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700956
Dan Albert8b72aef2015-03-23 19:13:21 -0700957
Daniel Norman55417142019-11-25 16:04:36 -0800958def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700959 """Merges dynamic partition info variables.
960
961 Args:
962 framework_dict: The dictionary of dynamic partition info variables from the
963 partial framework target files.
964 vendor_dict: The dictionary of dynamic partition info variables from the
965 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700966
967 Returns:
968 The merged dynamic partition info dictionary.
969 """
970 merged_dict = {}
971 # Partition groups and group sizes are defined by the vendor dict because
972 # these values may vary for each board that uses a shared system image.
973 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800974 framework_dynamic_partition_list = framework_dict.get(
975 "dynamic_partition_list", "")
976 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
977 merged_dict["dynamic_partition_list"] = ("%s %s" % (
978 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700979 for partition_group in merged_dict["super_partition_groups"].split(" "):
980 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800981 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700982 if key not in vendor_dict:
983 raise ValueError("Vendor dict does not contain required key %s." % key)
984 merged_dict[key] = vendor_dict[key]
985
986 # Set the partition group's partition list using a concatenation of the
987 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800988 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700989 merged_dict[key] = (
990 "%s %s" %
991 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530992
993 # Pick virtual ab related flags from vendor dict, if defined.
994 if "virtual_ab" in vendor_dict.keys():
995 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
996 if "virtual_ab_retrofit" in vendor_dict.keys():
997 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700998 return merged_dict
999
1000
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001001def AppendAVBSigningArgs(cmd, partition):
1002 """Append signing arguments for avbtool."""
1003 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1004 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001005 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1006 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1007 if os.path.exists(new_key_path):
1008 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001009 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1010 if key_path and algorithm:
1011 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001012 avb_salt = OPTIONS.info_dict.get("avb_salt")
1013 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001014 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001015 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001016
1017
Tao Bao765668f2019-10-04 22:03:00 -07001018def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001019 """Returns the VBMeta arguments for partition.
1020
1021 It sets up the VBMeta argument by including the partition descriptor from the
1022 given 'image', or by configuring the partition as a chained partition.
1023
1024 Args:
1025 partition: The name of the partition (e.g. "system").
1026 image: The path to the partition image.
1027 info_dict: A dict returned by common.LoadInfoDict(). Will use
1028 OPTIONS.info_dict if None has been given.
1029
1030 Returns:
1031 A list of VBMeta arguments.
1032 """
1033 if info_dict is None:
1034 info_dict = OPTIONS.info_dict
1035
1036 # Check if chain partition is used.
1037 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001038 if not key_path:
1039 return ["--include_descriptors_from_image", image]
1040
1041 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1042 # into vbmeta.img. The recovery image will be configured on an independent
1043 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1044 # See details at
1045 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001046 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001047 return []
1048
1049 # Otherwise chain the partition into vbmeta.
1050 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1051 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001052
1053
Tao Bao02a08592018-07-22 12:40:45 -07001054def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1055 """Constructs and returns the arg to build or verify a chained partition.
1056
1057 Args:
1058 partition: The partition name.
1059 info_dict: The info dict to look up the key info and rollback index
1060 location.
1061 key: The key to be used for building or verifying the partition. Defaults to
1062 the key listed in info_dict.
1063
1064 Returns:
1065 A string of form "partition:rollback_index_location:key" that can be used to
1066 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001067 """
1068 if key is None:
1069 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001070 if key and not os.path.exists(key) and OPTIONS.search_path:
1071 new_key_path = os.path.join(OPTIONS.search_path, key)
1072 if os.path.exists(new_key_path):
1073 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001074 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001075 rollback_index_location = info_dict[
1076 "avb_" + partition + "_rollback_index_location"]
1077 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1078
1079
Tianjie20dd8f22020-04-19 15:51:16 -07001080def ConstructAftlMakeImageCommands(output_image):
1081 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001082
1083 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001084 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001085 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1086 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1087 'No AFTL manufacturer key provided.'
1088
1089 vbmeta_image = MakeTempFile()
1090 os.rename(output_image, vbmeta_image)
1091 build_info = BuildInfo(OPTIONS.info_dict)
1092 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001093 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001094 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001095 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001096 "--vbmeta_image_path", vbmeta_image,
1097 "--output", output_image,
1098 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001099 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001100 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1101 "--algorithm", "SHA256_RSA4096",
1102 "--padding", "4096"]
1103 if OPTIONS.aftl_signer_helper:
1104 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001105 return aftl_cmd
1106
1107
1108def AddAftlInclusionProof(output_image):
1109 """Appends the aftl inclusion proof to the vbmeta image."""
1110
1111 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001112 RunAndCheckOutput(aftl_cmd)
1113
1114 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1115 output_image, '--transparency_log_pub_keys',
1116 OPTIONS.aftl_key_path]
1117 RunAndCheckOutput(verify_cmd)
1118
1119
Daniel Norman276f0622019-07-26 14:13:51 -07001120def BuildVBMeta(image_path, partitions, name, needed_partitions):
1121 """Creates a VBMeta image.
1122
1123 It generates the requested VBMeta image. The requested image could be for
1124 top-level or chained VBMeta image, which is determined based on the name.
1125
1126 Args:
1127 image_path: The output path for the new VBMeta image.
1128 partitions: A dict that's keyed by partition names with image paths as
1129 values. Only valid partition names are accepted, as listed in
1130 common.AVB_PARTITIONS.
1131 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1132 needed_partitions: Partitions whose descriptors should be included into the
1133 generated VBMeta image.
1134
1135 Raises:
1136 AssertionError: On invalid input args.
1137 """
1138 avbtool = OPTIONS.info_dict["avb_avbtool"]
1139 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1140 AppendAVBSigningArgs(cmd, name)
1141
1142 for partition, path in partitions.items():
1143 if partition not in needed_partitions:
1144 continue
1145 assert (partition in AVB_PARTITIONS or
1146 partition in AVB_VBMETA_PARTITIONS), \
1147 'Unknown partition: {}'.format(partition)
1148 assert os.path.exists(path), \
1149 'Failed to find {} for {}'.format(path, partition)
1150 cmd.extend(GetAvbPartitionArg(partition, path))
1151
1152 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1153 if args and args.strip():
1154 split_args = shlex.split(args)
1155 for index, arg in enumerate(split_args[:-1]):
1156 # Sanity check that the image file exists. Some images might be defined
1157 # as a path relative to source tree, which may not be available at the
1158 # same location when running this script (we have the input target_files
1159 # zip only). For such cases, we additionally scan other locations (e.g.
1160 # IMAGES/, RADIO/, etc) before bailing out.
1161 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001162 chained_image = split_args[index + 1]
1163 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001164 continue
1165 found = False
1166 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1167 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001168 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001169 if os.path.exists(alt_path):
1170 split_args[index + 1] = alt_path
1171 found = True
1172 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001173 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001174 cmd.extend(split_args)
1175
1176 RunAndCheckOutput(cmd)
1177
Tianjie Xueaed60c2020-03-12 00:33:28 -07001178 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001179 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001180 AddAftlInclusionProof(image_path)
1181
Daniel Norman276f0622019-07-26 14:13:51 -07001182
Steve Mucklee1b10862019-07-10 10:49:37 -07001183def _MakeRamdisk(sourcedir, fs_config_file=None):
1184 ramdisk_img = tempfile.NamedTemporaryFile()
1185
1186 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1187 cmd = ["mkbootfs", "-f", fs_config_file,
1188 os.path.join(sourcedir, "RAMDISK")]
1189 else:
1190 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1191 p1 = Run(cmd, stdout=subprocess.PIPE)
1192 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1193
1194 p2.wait()
1195 p1.wait()
1196 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1197 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1198
1199 return ramdisk_img
1200
1201
Steve Muckle9793cf62020-04-08 18:27:00 -07001202def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001203 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001204 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001205
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001206 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001207 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1208 we are building a two-step special image (i.e. building a recovery image to
1209 be loaded into /boot in two-step OTAs).
1210
1211 Return the image data, or None if sourcedir does not appear to contains files
1212 for building the requested image.
1213 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001214
Steve Muckle9793cf62020-04-08 18:27:00 -07001215 # "boot" or "recovery", without extension.
1216 partition_name = os.path.basename(sourcedir).lower()
1217
1218 if partition_name == "recovery":
1219 kernel = "kernel"
1220 else:
1221 kernel = image_name.replace("boot", "kernel")
1222 kernel = kernel.replace(".img","")
1223 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001224 return None
1225
1226 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001227 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001228
Doug Zongkerd5131602012-08-02 14:46:42 -07001229 if info_dict is None:
1230 info_dict = OPTIONS.info_dict
1231
Doug Zongkereef39442009-04-02 12:14:19 -07001232 img = tempfile.NamedTemporaryFile()
1233
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001234 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001235 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001236
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001237 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1238 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1239
Steve Muckle9793cf62020-04-08 18:27:00 -07001240 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001241
Benoit Fradina45a8682014-07-14 21:00:43 +02001242 fn = os.path.join(sourcedir, "second")
1243 if os.access(fn, os.F_OK):
1244 cmd.append("--second")
1245 cmd.append(fn)
1246
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001247 fn = os.path.join(sourcedir, "dtb")
1248 if os.access(fn, os.F_OK):
1249 cmd.append("--dtb")
1250 cmd.append(fn)
1251
Doug Zongker171f1cd2009-06-15 22:36:37 -07001252 fn = os.path.join(sourcedir, "cmdline")
1253 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001254 cmd.append("--cmdline")
1255 cmd.append(open(fn).read().rstrip("\n"))
1256
1257 fn = os.path.join(sourcedir, "base")
1258 if os.access(fn, os.F_OK):
1259 cmd.append("--base")
1260 cmd.append(open(fn).read().rstrip("\n"))
1261
Ying Wang4de6b5b2010-08-25 14:29:34 -07001262 fn = os.path.join(sourcedir, "pagesize")
1263 if os.access(fn, os.F_OK):
1264 cmd.append("--pagesize")
1265 cmd.append(open(fn).read().rstrip("\n"))
1266
Steve Mucklef84668e2020-03-16 19:13:46 -07001267 if partition_name == "recovery":
1268 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301269 if not args:
1270 # Fall back to "mkbootimg_args" for recovery image
1271 # in case "recovery_mkbootimg_args" is not set.
1272 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001273 else:
1274 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001275 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001276 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001277
Tao Bao76def242017-11-21 09:25:31 -08001278 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001279 if args and args.strip():
1280 cmd.extend(shlex.split(args))
1281
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001282 if has_ramdisk:
1283 cmd.extend(["--ramdisk", ramdisk_img.name])
1284
Tao Baod95e9fd2015-03-29 23:07:41 -07001285 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001286 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001287 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001288 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001289 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001290 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001291
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001292 if partition_name == "recovery":
1293 if info_dict.get("include_recovery_dtbo") == "true":
1294 fn = os.path.join(sourcedir, "recovery_dtbo")
1295 cmd.extend(["--recovery_dtbo", fn])
1296 if info_dict.get("include_recovery_acpio") == "true":
1297 fn = os.path.join(sourcedir, "recovery_acpio")
1298 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001299
Tao Bao986ee862018-10-04 15:46:16 -07001300 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001301
Tao Bao76def242017-11-21 09:25:31 -08001302 if (info_dict.get("boot_signer") == "true" and
1303 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001304 # Hard-code the path as "/boot" for two-step special recovery image (which
1305 # will be loaded into /boot during the two-step OTA).
1306 if two_step_image:
1307 path = "/boot"
1308 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001309 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001310 cmd = [OPTIONS.boot_signer_path]
1311 cmd.extend(OPTIONS.boot_signer_args)
1312 cmd.extend([path, img.name,
1313 info_dict["verity_key"] + ".pk8",
1314 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001315 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001316
Tao Baod95e9fd2015-03-29 23:07:41 -07001317 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001318 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001319 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001320 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001321 # We have switched from the prebuilt futility binary to using the tool
1322 # (futility-host) built from the source. Override the setting in the old
1323 # TF.zip.
1324 futility = info_dict["futility"]
1325 if futility.startswith("prebuilts/"):
1326 futility = "futility-host"
1327 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001328 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001329 info_dict["vboot_key"] + ".vbprivk",
1330 info_dict["vboot_subkey"] + ".vbprivk",
1331 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001332 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001333 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001334
Tao Baof3282b42015-04-01 11:21:55 -07001335 # Clean up the temp files.
1336 img_unsigned.close()
1337 img_keyblock.close()
1338
David Zeuthen8fecb282017-12-01 16:24:01 -05001339 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001340 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001341 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001342 if partition_name == "recovery":
1343 part_size = info_dict["recovery_size"]
1344 else:
1345 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001346 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001347 "--partition_size", str(part_size), "--partition_name",
1348 partition_name]
1349 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001350 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001351 if args and args.strip():
1352 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001353 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001354
1355 img.seek(os.SEEK_SET, 0)
1356 data = img.read()
1357
1358 if has_ramdisk:
1359 ramdisk_img.close()
1360 img.close()
1361
1362 return data
1363
1364
Doug Zongkerd5131602012-08-02 14:46:42 -07001365def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001366 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001367 """Return a File object with the desired bootable image.
1368
1369 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1370 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1371 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001372
Doug Zongker55d93282011-01-25 17:03:34 -08001373 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1374 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001375 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001376 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001377
1378 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1379 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001380 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001381 return File.FromLocalFile(name, prebuilt_path)
1382
Tao Bao32fcdab2018-10-12 10:30:39 -07001383 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001384
1385 if info_dict is None:
1386 info_dict = OPTIONS.info_dict
1387
1388 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001389 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1390 # for recovery.
1391 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1392 prebuilt_name != "boot.img" or
1393 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001394
Doug Zongker6f1d0312014-08-22 08:07:12 -07001395 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001396 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001397 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001398 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001399 if data:
1400 return File(name, data)
1401 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001402
Doug Zongkereef39442009-04-02 12:14:19 -07001403
Steve Mucklee1b10862019-07-10 10:49:37 -07001404def _BuildVendorBootImage(sourcedir, info_dict=None):
1405 """Build a vendor boot image from the specified sourcedir.
1406
1407 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1408 turn them into a vendor boot image.
1409
1410 Return the image data, or None if sourcedir does not appear to contains files
1411 for building the requested image.
1412 """
1413
1414 if info_dict is None:
1415 info_dict = OPTIONS.info_dict
1416
1417 img = tempfile.NamedTemporaryFile()
1418
1419 ramdisk_img = _MakeRamdisk(sourcedir)
1420
1421 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1422 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1423
1424 cmd = [mkbootimg]
1425
1426 fn = os.path.join(sourcedir, "dtb")
1427 if os.access(fn, os.F_OK):
1428 cmd.append("--dtb")
1429 cmd.append(fn)
1430
1431 fn = os.path.join(sourcedir, "vendor_cmdline")
1432 if os.access(fn, os.F_OK):
1433 cmd.append("--vendor_cmdline")
1434 cmd.append(open(fn).read().rstrip("\n"))
1435
1436 fn = os.path.join(sourcedir, "base")
1437 if os.access(fn, os.F_OK):
1438 cmd.append("--base")
1439 cmd.append(open(fn).read().rstrip("\n"))
1440
1441 fn = os.path.join(sourcedir, "pagesize")
1442 if os.access(fn, os.F_OK):
1443 cmd.append("--pagesize")
1444 cmd.append(open(fn).read().rstrip("\n"))
1445
1446 args = info_dict.get("mkbootimg_args")
1447 if args and args.strip():
1448 cmd.extend(shlex.split(args))
1449
1450 args = info_dict.get("mkbootimg_version_args")
1451 if args and args.strip():
1452 cmd.extend(shlex.split(args))
1453
1454 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1455 cmd.extend(["--vendor_boot", img.name])
1456
1457 RunAndCheckOutput(cmd)
1458
1459 # AVB: if enabled, calculate and add hash.
1460 if info_dict.get("avb_enable") == "true":
1461 avbtool = info_dict["avb_avbtool"]
1462 part_size = info_dict["vendor_boot_size"]
1463 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001464 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001465 AppendAVBSigningArgs(cmd, "vendor_boot")
1466 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1467 if args and args.strip():
1468 cmd.extend(shlex.split(args))
1469 RunAndCheckOutput(cmd)
1470
1471 img.seek(os.SEEK_SET, 0)
1472 data = img.read()
1473
1474 ramdisk_img.close()
1475 img.close()
1476
1477 return data
1478
1479
1480def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1481 info_dict=None):
1482 """Return a File object with the desired vendor boot image.
1483
1484 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1485 the source files in 'unpack_dir'/'tree_subdir'."""
1486
1487 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1488 if os.path.exists(prebuilt_path):
1489 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1490 return File.FromLocalFile(name, prebuilt_path)
1491
1492 logger.info("building image from target_files %s...", tree_subdir)
1493
1494 if info_dict is None:
1495 info_dict = OPTIONS.info_dict
1496
1497 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1498 if data:
1499 return File(name, data)
1500 return None
1501
1502
Narayan Kamatha07bf042017-08-14 14:49:21 +01001503def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001504 """Gunzips the given gzip compressed file to a given output file."""
1505 with gzip.open(in_filename, "rb") as in_file, \
1506 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001507 shutil.copyfileobj(in_file, out_file)
1508
1509
Tao Bao0ff15de2019-03-20 11:26:06 -07001510def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001511 """Unzips the archive to the given directory.
1512
1513 Args:
1514 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001515 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001516 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1517 archvie. Non-matching patterns will be filtered out. If there's no match
1518 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001519 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001520 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001521 if patterns is not None:
1522 # Filter out non-matching patterns. unzip will complain otherwise.
1523 with zipfile.ZipFile(filename) as input_zip:
1524 names = input_zip.namelist()
1525 filtered = [
1526 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1527
1528 # There isn't any matching files. Don't unzip anything.
1529 if not filtered:
1530 return
1531 cmd.extend(filtered)
1532
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001533 RunAndCheckOutput(cmd)
1534
1535
Doug Zongker75f17362009-12-08 13:46:44 -08001536def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001537 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001538
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001539 Args:
1540 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1541 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1542
1543 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1544 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001545
Tao Bao1c830bf2017-12-25 10:43:47 -08001546 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001547 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001548 """
Doug Zongkereef39442009-04-02 12:14:19 -07001549
Tao Bao1c830bf2017-12-25 10:43:47 -08001550 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001551 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1552 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001553 UnzipToDir(m.group(1), tmp, pattern)
1554 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001555 filename = m.group(1)
1556 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001557 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001558
Tao Baodba59ee2018-01-09 13:21:02 -08001559 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001560
1561
Yifan Hong8a66a712019-04-04 15:37:57 -07001562def GetUserImage(which, tmpdir, input_zip,
1563 info_dict=None,
1564 allow_shared_blocks=None,
1565 hashtree_info_generator=None,
1566 reset_file_map=False):
1567 """Returns an Image object suitable for passing to BlockImageDiff.
1568
1569 This function loads the specified image from the given path. If the specified
1570 image is sparse, it also performs additional processing for OTA purpose. For
1571 example, it always adds block 0 to clobbered blocks list. It also detects
1572 files that cannot be reconstructed from the block list, for whom we should
1573 avoid applying imgdiff.
1574
1575 Args:
1576 which: The partition name.
1577 tmpdir: The directory that contains the prebuilt image and block map file.
1578 input_zip: The target-files ZIP archive.
1579 info_dict: The dict to be looked up for relevant info.
1580 allow_shared_blocks: If image is sparse, whether having shared blocks is
1581 allowed. If none, it is looked up from info_dict.
1582 hashtree_info_generator: If present and image is sparse, generates the
1583 hashtree_info for this sparse image.
1584 reset_file_map: If true and image is sparse, reset file map before returning
1585 the image.
1586 Returns:
1587 A Image object. If it is a sparse image and reset_file_map is False, the
1588 image will have file_map info loaded.
1589 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001590 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001591 info_dict = LoadInfoDict(input_zip)
1592
1593 is_sparse = info_dict.get("extfs_sparse_flag")
1594
1595 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1596 # shared blocks (i.e. some blocks will show up in multiple files' block
1597 # list). We can only allocate such shared blocks to the first "owner", and
1598 # disable imgdiff for all later occurrences.
1599 if allow_shared_blocks is None:
1600 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1601
1602 if is_sparse:
1603 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1604 hashtree_info_generator)
1605 if reset_file_map:
1606 img.ResetFileMap()
1607 return img
1608 else:
1609 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1610
1611
1612def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1613 """Returns a Image object suitable for passing to BlockImageDiff.
1614
1615 This function loads the specified non-sparse image from the given path.
1616
1617 Args:
1618 which: The partition name.
1619 tmpdir: The directory that contains the prebuilt image and block map file.
1620 Returns:
1621 A Image object.
1622 """
1623 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1624 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1625
1626 # The image and map files must have been created prior to calling
1627 # ota_from_target_files.py (since LMP).
1628 assert os.path.exists(path) and os.path.exists(mappath)
1629
Tianjie Xu41976c72019-07-03 13:57:01 -07001630 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1631
Yifan Hong8a66a712019-04-04 15:37:57 -07001632
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001633def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1634 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001635 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1636
1637 This function loads the specified sparse image from the given path, and
1638 performs additional processing for OTA purpose. For example, it always adds
1639 block 0 to clobbered blocks list. It also detects files that cannot be
1640 reconstructed from the block list, for whom we should avoid applying imgdiff.
1641
1642 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001643 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001644 tmpdir: The directory that contains the prebuilt image and block map file.
1645 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001646 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001647 hashtree_info_generator: If present, generates the hashtree_info for this
1648 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001649 Returns:
1650 A SparseImage object, with file_map info loaded.
1651 """
Tao Baoc765cca2018-01-31 17:32:40 -08001652 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1653 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1654
1655 # The image and map files must have been created prior to calling
1656 # ota_from_target_files.py (since LMP).
1657 assert os.path.exists(path) and os.path.exists(mappath)
1658
1659 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1660 # it to clobbered_blocks so that it will be written to the target
1661 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1662 clobbered_blocks = "0"
1663
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001664 image = sparse_img.SparseImage(
1665 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1666 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001667
1668 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1669 # if they contain all zeros. We can't reconstruct such a file from its block
1670 # list. Tag such entries accordingly. (Bug: 65213616)
1671 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001672 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001673 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001674 continue
1675
Tom Cherryd14b8952018-08-09 14:26:00 -07001676 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1677 # filename listed in system.map may contain an additional leading slash
1678 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1679 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001680 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001681
Tom Cherryd14b8952018-08-09 14:26:00 -07001682 # Special handling another case, where files not under /system
1683 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001684 if which == 'system' and not arcname.startswith('SYSTEM'):
1685 arcname = 'ROOT/' + arcname
1686
1687 assert arcname in input_zip.namelist(), \
1688 "Failed to find the ZIP entry for {}".format(entry)
1689
Tao Baoc765cca2018-01-31 17:32:40 -08001690 info = input_zip.getinfo(arcname)
1691 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001692
1693 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001694 # image, check the original block list to determine its completeness. Note
1695 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001696 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001697 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001698
Tao Baoc765cca2018-01-31 17:32:40 -08001699 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1700 ranges.extra['incomplete'] = True
1701
1702 return image
1703
1704
Doug Zongkereef39442009-04-02 12:14:19 -07001705def GetKeyPasswords(keylist):
1706 """Given a list of keys, prompt the user to enter passwords for
1707 those which require them. Return a {key: password} dict. password
1708 will be None if the key has no password."""
1709
Doug Zongker8ce7c252009-05-22 13:34:54 -07001710 no_passwords = []
1711 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001712 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001713 devnull = open("/dev/null", "w+b")
1714 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001715 # We don't need a password for things that aren't really keys.
1716 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001717 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001718 continue
1719
T.R. Fullhart37e10522013-03-18 10:31:26 -07001720 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001721 "-inform", "DER", "-nocrypt"],
1722 stdin=devnull.fileno(),
1723 stdout=devnull.fileno(),
1724 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001725 p.communicate()
1726 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001727 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001728 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001729 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001730 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1731 "-inform", "DER", "-passin", "pass:"],
1732 stdin=devnull.fileno(),
1733 stdout=devnull.fileno(),
1734 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001735 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001736 if p.returncode == 0:
1737 # Encrypted key with empty string as password.
1738 key_passwords[k] = ''
1739 elif stderr.startswith('Error decrypting key'):
1740 # Definitely encrypted key.
1741 # It would have said "Error reading key" if it didn't parse correctly.
1742 need_passwords.append(k)
1743 else:
1744 # Potentially, a type of key that openssl doesn't understand.
1745 # We'll let the routines in signapk.jar handle it.
1746 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001747 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001748
T.R. Fullhart37e10522013-03-18 10:31:26 -07001749 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001750 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001751 return key_passwords
1752
1753
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001754def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001755 """Gets the minSdkVersion declared in the APK.
1756
changho.shin0f125362019-07-08 10:59:00 +09001757 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001758 This can be both a decimal number (API Level) or a codename.
1759
1760 Args:
1761 apk_name: The APK filename.
1762
1763 Returns:
1764 The parsed SDK version string.
1765
1766 Raises:
1767 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001768 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001769 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001770 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001771 stderr=subprocess.PIPE)
1772 stdoutdata, stderrdata = proc.communicate()
1773 if proc.returncode != 0:
1774 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001775 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001776 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001777
Tao Baof47bf0f2018-03-21 23:28:51 -07001778 for line in stdoutdata.split("\n"):
1779 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001780 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1781 if m:
1782 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001783 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001784
1785
1786def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001787 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001788
Tao Baof47bf0f2018-03-21 23:28:51 -07001789 If minSdkVersion is set to a codename, it is translated to a number using the
1790 provided map.
1791
1792 Args:
1793 apk_name: The APK filename.
1794
1795 Returns:
1796 The parsed SDK version number.
1797
1798 Raises:
1799 ExternalError: On failing to get the min SDK version number.
1800 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001801 version = GetMinSdkVersion(apk_name)
1802 try:
1803 return int(version)
1804 except ValueError:
1805 # Not a decimal number. Codename?
1806 if version in codename_to_api_level_map:
1807 return codename_to_api_level_map[version]
1808 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001809 raise ExternalError(
1810 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1811 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001812
1813
1814def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001815 codename_to_api_level_map=None, whole_file=False,
1816 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001817 """Sign the input_name zip/jar/apk, producing output_name. Use the
1818 given key and password (the latter may be None if the key does not
1819 have a password.
1820
Doug Zongker951495f2009-08-14 12:44:19 -07001821 If whole_file is true, use the "-w" option to SignApk to embed a
1822 signature that covers the whole file in the archive comment of the
1823 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001824
1825 min_api_level is the API Level (int) of the oldest platform this file may end
1826 up on. If not specified for an APK, the API Level is obtained by interpreting
1827 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1828
1829 codename_to_api_level_map is needed to translate the codename which may be
1830 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001831
1832 Caller may optionally specify extra args to be passed to SignApk, which
1833 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001834 """
Tao Bao76def242017-11-21 09:25:31 -08001835 if codename_to_api_level_map is None:
1836 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001837 if extra_signapk_args is None:
1838 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001839
Alex Klyubin9667b182015-12-10 13:38:50 -08001840 java_library_path = os.path.join(
1841 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1842
Tao Baoe95540e2016-11-08 12:08:53 -08001843 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1844 ["-Djava.library.path=" + java_library_path,
1845 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001846 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001847 if whole_file:
1848 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001849
1850 min_sdk_version = min_api_level
1851 if min_sdk_version is None:
1852 if not whole_file:
1853 min_sdk_version = GetMinSdkVersionInt(
1854 input_name, codename_to_api_level_map)
1855 if min_sdk_version is not None:
1856 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1857
T.R. Fullhart37e10522013-03-18 10:31:26 -07001858 cmd.extend([key + OPTIONS.public_key_suffix,
1859 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001860 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001861
Tao Bao73dd4f42018-10-04 16:25:33 -07001862 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001863 if password is not None:
1864 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001865 stdoutdata, _ = proc.communicate(password)
1866 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001867 raise ExternalError(
1868 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001869 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001870
Doug Zongkereef39442009-04-02 12:14:19 -07001871
Doug Zongker37974732010-09-16 17:44:38 -07001872def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001873 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001874
Tao Bao9dd909e2017-11-14 11:27:32 -08001875 For non-AVB images, raise exception if the data is too big. Print a warning
1876 if the data is nearing the maximum size.
1877
1878 For AVB images, the actual image size should be identical to the limit.
1879
1880 Args:
1881 data: A string that contains all the data for the partition.
1882 target: The partition name. The ".img" suffix is optional.
1883 info_dict: The dict to be looked up for relevant info.
1884 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001885 if target.endswith(".img"):
1886 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001887 mount_point = "/" + target
1888
Ying Wangf8824af2014-06-03 14:07:27 -07001889 fs_type = None
1890 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001891 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001892 if mount_point == "/userdata":
1893 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001894 p = info_dict["fstab"][mount_point]
1895 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001896 device = p.device
1897 if "/" in device:
1898 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001899 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001900 if not fs_type or not limit:
1901 return
Doug Zongkereef39442009-04-02 12:14:19 -07001902
Andrew Boie0f9aec82012-02-14 09:32:52 -08001903 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001904 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1905 # path.
1906 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1907 if size != limit:
1908 raise ExternalError(
1909 "Mismatching image size for %s: expected %d actual %d" % (
1910 target, limit, size))
1911 else:
1912 pct = float(size) * 100.0 / limit
1913 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1914 if pct >= 99.0:
1915 raise ExternalError(msg)
1916 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001917 logger.warning("\n WARNING: %s\n", msg)
1918 else:
1919 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001920
1921
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001922def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001923 """Parses the APK certs info from a given target-files zip.
1924
1925 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1926 tuple with the following elements: (1) a dictionary that maps packages to
1927 certs (based on the "certificate" and "private_key" attributes in the file;
1928 (2) a string representing the extension of compressed APKs in the target files
1929 (e.g ".gz", ".bro").
1930
1931 Args:
1932 tf_zip: The input target_files ZipFile (already open).
1933
1934 Returns:
1935 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1936 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1937 no compressed APKs.
1938 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001939 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001940 compressed_extension = None
1941
Tao Bao0f990332017-09-08 19:02:54 -07001942 # META/apkcerts.txt contains the info for _all_ the packages known at build
1943 # time. Filter out the ones that are not installed.
1944 installed_files = set()
1945 for name in tf_zip.namelist():
1946 basename = os.path.basename(name)
1947 if basename:
1948 installed_files.add(basename)
1949
Tao Baoda30cfa2017-12-01 16:19:46 -08001950 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001951 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001952 if not line:
1953 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001954 m = re.match(
1955 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001956 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1957 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001958 line)
1959 if not m:
1960 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001961
Tao Bao818ddf52018-01-05 11:17:34 -08001962 matches = m.groupdict()
1963 cert = matches["CERT"]
1964 privkey = matches["PRIVKEY"]
1965 name = matches["NAME"]
1966 this_compressed_extension = matches["COMPRESSED"]
1967
1968 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1969 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1970 if cert in SPECIAL_CERT_STRINGS and not privkey:
1971 certmap[name] = cert
1972 elif (cert.endswith(OPTIONS.public_key_suffix) and
1973 privkey.endswith(OPTIONS.private_key_suffix) and
1974 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1975 certmap[name] = cert[:-public_key_suffix_len]
1976 else:
1977 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1978
1979 if not this_compressed_extension:
1980 continue
1981
1982 # Only count the installed files.
1983 filename = name + '.' + this_compressed_extension
1984 if filename not in installed_files:
1985 continue
1986
1987 # Make sure that all the values in the compression map have the same
1988 # extension. We don't support multiple compression methods in the same
1989 # system image.
1990 if compressed_extension:
1991 if this_compressed_extension != compressed_extension:
1992 raise ValueError(
1993 "Multiple compressed extensions: {} vs {}".format(
1994 compressed_extension, this_compressed_extension))
1995 else:
1996 compressed_extension = this_compressed_extension
1997
1998 return (certmap,
1999 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002000
2001
Doug Zongkereef39442009-04-02 12:14:19 -07002002COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002003Global options
2004
2005 -p (--path) <dir>
2006 Prepend <dir>/bin to the list of places to search for binaries run by this
2007 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002008
Doug Zongker05d3dea2009-06-22 11:32:31 -07002009 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002010 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002011
Tao Bao30df8b42018-04-23 15:32:53 -07002012 -x (--extra) <key=value>
2013 Add a key/value pair to the 'extras' dict, which device-specific extension
2014 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002015
Doug Zongkereef39442009-04-02 12:14:19 -07002016 -v (--verbose)
2017 Show command lines being executed.
2018
2019 -h (--help)
2020 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002021
2022 --logfile <file>
2023 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002024"""
2025
2026def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002027 print(docstring.rstrip("\n"))
2028 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002029
2030
2031def ParseOptions(argv,
2032 docstring,
2033 extra_opts="", extra_long_opts=(),
2034 extra_option_handler=None):
2035 """Parse the options in argv and return any arguments that aren't
2036 flags. docstring is the calling module's docstring, to be displayed
2037 for errors and -h. extra_opts and extra_long_opts are for flags
2038 defined by the caller, which are processed by passing them to
2039 extra_option_handler."""
2040
2041 try:
2042 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002043 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002044 ["help", "verbose", "path=", "signapk_path=",
2045 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002046 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002047 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2048 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002049 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2050 "aftl_key_path=", "aftl_manufacturer_key_path=",
2051 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002052 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002053 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002054 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002055 sys.exit(2)
2056
Doug Zongkereef39442009-04-02 12:14:19 -07002057 for o, a in opts:
2058 if o in ("-h", "--help"):
2059 Usage(docstring)
2060 sys.exit()
2061 elif o in ("-v", "--verbose"):
2062 OPTIONS.verbose = True
2063 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002064 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002065 elif o in ("--signapk_path",):
2066 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002067 elif o in ("--signapk_shared_library_path",):
2068 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002069 elif o in ("--extra_signapk_args",):
2070 OPTIONS.extra_signapk_args = shlex.split(a)
2071 elif o in ("--java_path",):
2072 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002073 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002074 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002075 elif o in ("--android_jar_path",):
2076 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002077 elif o in ("--public_key_suffix",):
2078 OPTIONS.public_key_suffix = a
2079 elif o in ("--private_key_suffix",):
2080 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002081 elif o in ("--boot_signer_path",):
2082 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002083 elif o in ("--boot_signer_args",):
2084 OPTIONS.boot_signer_args = shlex.split(a)
2085 elif o in ("--verity_signer_path",):
2086 OPTIONS.verity_signer_path = a
2087 elif o in ("--verity_signer_args",):
2088 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002089 elif o in ("--aftl_tool_path",):
2090 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002091 elif o in ("--aftl_server",):
2092 OPTIONS.aftl_server = a
2093 elif o in ("--aftl_key_path",):
2094 OPTIONS.aftl_key_path = a
2095 elif o in ("--aftl_manufacturer_key_path",):
2096 OPTIONS.aftl_manufacturer_key_path = a
2097 elif o in ("--aftl_signer_helper",):
2098 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002099 elif o in ("-s", "--device_specific"):
2100 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002101 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002102 key, value = a.split("=", 1)
2103 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002104 elif o in ("--logfile",):
2105 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002106 else:
2107 if extra_option_handler is None or not extra_option_handler(o, a):
2108 assert False, "unknown option \"%s\"" % (o,)
2109
Doug Zongker85448772014-09-09 14:59:20 -07002110 if OPTIONS.search_path:
2111 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2112 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002113
2114 return args
2115
2116
Tao Bao4c851b12016-09-19 13:54:38 -07002117def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002118 """Make a temp file and add it to the list of things to be deleted
2119 when Cleanup() is called. Return the filename."""
2120 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2121 os.close(fd)
2122 OPTIONS.tempfiles.append(fn)
2123 return fn
2124
2125
Tao Bao1c830bf2017-12-25 10:43:47 -08002126def MakeTempDir(prefix='tmp', suffix=''):
2127 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2128
2129 Returns:
2130 The absolute pathname of the new directory.
2131 """
2132 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2133 OPTIONS.tempfiles.append(dir_name)
2134 return dir_name
2135
2136
Doug Zongkereef39442009-04-02 12:14:19 -07002137def Cleanup():
2138 for i in OPTIONS.tempfiles:
2139 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002140 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002141 else:
2142 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002143 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002144
2145
2146class PasswordManager(object):
2147 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002148 self.editor = os.getenv("EDITOR")
2149 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002150
2151 def GetPasswords(self, items):
2152 """Get passwords corresponding to each string in 'items',
2153 returning a dict. (The dict may have keys in addition to the
2154 values in 'items'.)
2155
2156 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2157 user edit that file to add more needed passwords. If no editor is
2158 available, or $ANDROID_PW_FILE isn't define, prompts the user
2159 interactively in the ordinary way.
2160 """
2161
2162 current = self.ReadFile()
2163
2164 first = True
2165 while True:
2166 missing = []
2167 for i in items:
2168 if i not in current or not current[i]:
2169 missing.append(i)
2170 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002171 if not missing:
2172 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002173
2174 for i in missing:
2175 current[i] = ""
2176
2177 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002178 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002179 if sys.version_info[0] >= 3:
2180 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002181 answer = raw_input("try to edit again? [y]> ").strip()
2182 if answer and answer[0] not in 'yY':
2183 raise RuntimeError("key passwords unavailable")
2184 first = False
2185
2186 current = self.UpdateAndReadFile(current)
2187
Dan Albert8b72aef2015-03-23 19:13:21 -07002188 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002189 """Prompt the user to enter a value (password) for each key in
2190 'current' whose value is fales. Returns a new dict with all the
2191 values.
2192 """
2193 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002194 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002195 if v:
2196 result[k] = v
2197 else:
2198 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002199 result[k] = getpass.getpass(
2200 "Enter password for %s key> " % k).strip()
2201 if result[k]:
2202 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002203 return result
2204
2205 def UpdateAndReadFile(self, current):
2206 if not self.editor or not self.pwfile:
2207 return self.PromptResult(current)
2208
2209 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002210 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002211 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2212 f.write("# (Additional spaces are harmless.)\n\n")
2213
2214 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002215 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002216 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002217 f.write("[[[ %s ]]] %s\n" % (v, k))
2218 if not v and first_line is None:
2219 # position cursor on first line with no password.
2220 first_line = i + 4
2221 f.close()
2222
Tao Bao986ee862018-10-04 15:46:16 -07002223 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002224
2225 return self.ReadFile()
2226
2227 def ReadFile(self):
2228 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002229 if self.pwfile is None:
2230 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002231 try:
2232 f = open(self.pwfile, "r")
2233 for line in f:
2234 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002235 if not line or line[0] == '#':
2236 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002237 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2238 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002239 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002240 else:
2241 result[m.group(2)] = m.group(1)
2242 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002243 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002244 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002245 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002246 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002247
2248
Dan Albert8e0178d2015-01-27 15:53:15 -08002249def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2250 compress_type=None):
2251 import datetime
2252
2253 # http://b/18015246
2254 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2255 # for files larger than 2GiB. We can work around this by adjusting their
2256 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2257 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2258 # it isn't clear to me exactly what circumstances cause this).
2259 # `zipfile.write()` must be used directly to work around this.
2260 #
2261 # This mess can be avoided if we port to python3.
2262 saved_zip64_limit = zipfile.ZIP64_LIMIT
2263 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2264
2265 if compress_type is None:
2266 compress_type = zip_file.compression
2267 if arcname is None:
2268 arcname = filename
2269
2270 saved_stat = os.stat(filename)
2271
2272 try:
2273 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2274 # file to be zipped and reset it when we're done.
2275 os.chmod(filename, perms)
2276
2277 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002278 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2279 # intentional. zip stores datetimes in local time without a time zone
2280 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2281 # in the zip archive.
2282 local_epoch = datetime.datetime.fromtimestamp(0)
2283 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002284 os.utime(filename, (timestamp, timestamp))
2285
2286 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2287 finally:
2288 os.chmod(filename, saved_stat.st_mode)
2289 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2290 zipfile.ZIP64_LIMIT = saved_zip64_limit
2291
2292
Tao Bao58c1b962015-05-20 09:32:18 -07002293def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002294 compress_type=None):
2295 """Wrap zipfile.writestr() function to work around the zip64 limit.
2296
2297 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2298 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2299 when calling crc32(bytes).
2300
2301 But it still works fine to write a shorter string into a large zip file.
2302 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2303 when we know the string won't be too long.
2304 """
2305
2306 saved_zip64_limit = zipfile.ZIP64_LIMIT
2307 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2308
2309 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2310 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002311 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002312 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002313 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002314 else:
Tao Baof3282b42015-04-01 11:21:55 -07002315 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002316 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2317 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2318 # such a case (since
2319 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2320 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2321 # permission bits. We follow the logic in Python 3 to get consistent
2322 # behavior between using the two versions.
2323 if not zinfo.external_attr:
2324 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002325
2326 # If compress_type is given, it overrides the value in zinfo.
2327 if compress_type is not None:
2328 zinfo.compress_type = compress_type
2329
Tao Bao58c1b962015-05-20 09:32:18 -07002330 # If perms is given, it has a priority.
2331 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002332 # If perms doesn't set the file type, mark it as a regular file.
2333 if perms & 0o770000 == 0:
2334 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002335 zinfo.external_attr = perms << 16
2336
Tao Baof3282b42015-04-01 11:21:55 -07002337 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002338 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2339
Dan Albert8b72aef2015-03-23 19:13:21 -07002340 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002341 zipfile.ZIP64_LIMIT = saved_zip64_limit
2342
2343
Tao Bao89d7ab22017-12-14 17:05:33 -08002344def ZipDelete(zip_filename, entries):
2345 """Deletes entries from a ZIP file.
2346
2347 Since deleting entries from a ZIP file is not supported, it shells out to
2348 'zip -d'.
2349
2350 Args:
2351 zip_filename: The name of the ZIP file.
2352 entries: The name of the entry, or the list of names to be deleted.
2353
2354 Raises:
2355 AssertionError: In case of non-zero return from 'zip'.
2356 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002357 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002358 entries = [entries]
2359 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002360 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002361
2362
Tao Baof3282b42015-04-01 11:21:55 -07002363def ZipClose(zip_file):
2364 # http://b/18015246
2365 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2366 # central directory.
2367 saved_zip64_limit = zipfile.ZIP64_LIMIT
2368 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2369
2370 zip_file.close()
2371
2372 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002373
2374
2375class DeviceSpecificParams(object):
2376 module = None
2377 def __init__(self, **kwargs):
2378 """Keyword arguments to the constructor become attributes of this
2379 object, which is passed to all functions in the device-specific
2380 module."""
Tao Bao38884282019-07-10 22:20:56 -07002381 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002382 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002383 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002384
2385 if self.module is None:
2386 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002387 if not path:
2388 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002389 try:
2390 if os.path.isdir(path):
2391 info = imp.find_module("releasetools", [path])
2392 else:
2393 d, f = os.path.split(path)
2394 b, x = os.path.splitext(f)
2395 if x == ".py":
2396 f = b
2397 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002398 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002399 self.module = imp.load_module("device_specific", *info)
2400 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002401 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002402
2403 def _DoCall(self, function_name, *args, **kwargs):
2404 """Call the named function in the device-specific module, passing
2405 the given args and kwargs. The first argument to the call will be
2406 the DeviceSpecific object itself. If there is no module, or the
2407 module does not define the function, return the value of the
2408 'default' kwarg (which itself defaults to None)."""
2409 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002410 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002411 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2412
2413 def FullOTA_Assertions(self):
2414 """Called after emitting the block of assertions at the top of a
2415 full OTA package. Implementations can add whatever additional
2416 assertions they like."""
2417 return self._DoCall("FullOTA_Assertions")
2418
Doug Zongkere5ff5902012-01-17 10:55:37 -08002419 def FullOTA_InstallBegin(self):
2420 """Called at the start of full OTA installation."""
2421 return self._DoCall("FullOTA_InstallBegin")
2422
Yifan Hong10c530d2018-12-27 17:34:18 -08002423 def FullOTA_GetBlockDifferences(self):
2424 """Called during full OTA installation and verification.
2425 Implementation should return a list of BlockDifference objects describing
2426 the update on each additional partitions.
2427 """
2428 return self._DoCall("FullOTA_GetBlockDifferences")
2429
Doug Zongker05d3dea2009-06-22 11:32:31 -07002430 def FullOTA_InstallEnd(self):
2431 """Called at the end of full OTA installation; typically this is
2432 used to install the image for the device's baseband processor."""
2433 return self._DoCall("FullOTA_InstallEnd")
2434
2435 def IncrementalOTA_Assertions(self):
2436 """Called after emitting the block of assertions at the top of an
2437 incremental OTA package. Implementations can add whatever
2438 additional assertions they like."""
2439 return self._DoCall("IncrementalOTA_Assertions")
2440
Doug Zongkere5ff5902012-01-17 10:55:37 -08002441 def IncrementalOTA_VerifyBegin(self):
2442 """Called at the start of the verification phase of incremental
2443 OTA installation; additional checks can be placed here to abort
2444 the script before any changes are made."""
2445 return self._DoCall("IncrementalOTA_VerifyBegin")
2446
Doug Zongker05d3dea2009-06-22 11:32:31 -07002447 def IncrementalOTA_VerifyEnd(self):
2448 """Called at the end of the verification phase of incremental OTA
2449 installation; additional checks can be placed here to abort the
2450 script before any changes are made."""
2451 return self._DoCall("IncrementalOTA_VerifyEnd")
2452
Doug Zongkere5ff5902012-01-17 10:55:37 -08002453 def IncrementalOTA_InstallBegin(self):
2454 """Called at the start of incremental OTA installation (after
2455 verification is complete)."""
2456 return self._DoCall("IncrementalOTA_InstallBegin")
2457
Yifan Hong10c530d2018-12-27 17:34:18 -08002458 def IncrementalOTA_GetBlockDifferences(self):
2459 """Called during incremental OTA installation and verification.
2460 Implementation should return a list of BlockDifference objects describing
2461 the update on each additional partitions.
2462 """
2463 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2464
Doug Zongker05d3dea2009-06-22 11:32:31 -07002465 def IncrementalOTA_InstallEnd(self):
2466 """Called at the end of incremental OTA installation; typically
2467 this is used to install the image for the device's baseband
2468 processor."""
2469 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002470
Tao Bao9bc6bb22015-11-09 16:58:28 -08002471 def VerifyOTA_Assertions(self):
2472 return self._DoCall("VerifyOTA_Assertions")
2473
Tao Bao76def242017-11-21 09:25:31 -08002474
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002475class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002476 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002477 self.name = name
2478 self.data = data
2479 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002480 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002481 self.sha1 = sha1(data).hexdigest()
2482
2483 @classmethod
2484 def FromLocalFile(cls, name, diskname):
2485 f = open(diskname, "rb")
2486 data = f.read()
2487 f.close()
2488 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002489
2490 def WriteToTemp(self):
2491 t = tempfile.NamedTemporaryFile()
2492 t.write(self.data)
2493 t.flush()
2494 return t
2495
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002496 def WriteToDir(self, d):
2497 with open(os.path.join(d, self.name), "wb") as fp:
2498 fp.write(self.data)
2499
Geremy Condra36bd3652014-02-06 19:45:10 -08002500 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002501 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002502
Tao Bao76def242017-11-21 09:25:31 -08002503
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002504DIFF_PROGRAM_BY_EXT = {
2505 ".gz" : "imgdiff",
2506 ".zip" : ["imgdiff", "-z"],
2507 ".jar" : ["imgdiff", "-z"],
2508 ".apk" : ["imgdiff", "-z"],
2509 ".img" : "imgdiff",
2510 }
2511
Tao Bao76def242017-11-21 09:25:31 -08002512
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002513class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002514 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002515 self.tf = tf
2516 self.sf = sf
2517 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002518 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519
2520 def ComputePatch(self):
2521 """Compute the patch (as a string of data) needed to turn sf into
2522 tf. Returns the same tuple as GetPatch()."""
2523
2524 tf = self.tf
2525 sf = self.sf
2526
Doug Zongker24cd2802012-08-14 16:36:15 -07002527 if self.diff_program:
2528 diff_program = self.diff_program
2529 else:
2530 ext = os.path.splitext(tf.name)[1]
2531 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002532
2533 ttemp = tf.WriteToTemp()
2534 stemp = sf.WriteToTemp()
2535
2536 ext = os.path.splitext(tf.name)[1]
2537
2538 try:
2539 ptemp = tempfile.NamedTemporaryFile()
2540 if isinstance(diff_program, list):
2541 cmd = copy.copy(diff_program)
2542 else:
2543 cmd = [diff_program]
2544 cmd.append(stemp.name)
2545 cmd.append(ttemp.name)
2546 cmd.append(ptemp.name)
2547 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002548 err = []
2549 def run():
2550 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002551 if e:
2552 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002553 th = threading.Thread(target=run)
2554 th.start()
2555 th.join(timeout=300) # 5 mins
2556 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002557 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002558 p.terminate()
2559 th.join(5)
2560 if th.is_alive():
2561 p.kill()
2562 th.join()
2563
Tianjie Xua2a9f992018-01-05 15:15:54 -08002564 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002565 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002566 self.patch = None
2567 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002568 diff = ptemp.read()
2569 finally:
2570 ptemp.close()
2571 stemp.close()
2572 ttemp.close()
2573
2574 self.patch = diff
2575 return self.tf, self.sf, self.patch
2576
2577
2578 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002579 """Returns a tuple of (target_file, source_file, patch_data).
2580
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002581 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002582 computing the patch failed.
2583 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002584 return self.tf, self.sf, self.patch
2585
2586
2587def ComputeDifferences(diffs):
2588 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002589 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002590
2591 # Do the largest files first, to try and reduce the long-pole effect.
2592 by_size = [(i.tf.size, i) for i in diffs]
2593 by_size.sort(reverse=True)
2594 by_size = [i[1] for i in by_size]
2595
2596 lock = threading.Lock()
2597 diff_iter = iter(by_size) # accessed under lock
2598
2599 def worker():
2600 try:
2601 lock.acquire()
2602 for d in diff_iter:
2603 lock.release()
2604 start = time.time()
2605 d.ComputePatch()
2606 dur = time.time() - start
2607 lock.acquire()
2608
2609 tf, sf, patch = d.GetPatch()
2610 if sf.name == tf.name:
2611 name = tf.name
2612 else:
2613 name = "%s (%s)" % (tf.name, sf.name)
2614 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002615 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002616 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002617 logger.info(
2618 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2619 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002620 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002621 except Exception:
2622 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002623 raise
2624
2625 # start worker threads; wait for them all to finish.
2626 threads = [threading.Thread(target=worker)
2627 for i in range(OPTIONS.worker_threads)]
2628 for th in threads:
2629 th.start()
2630 while threads:
2631 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002632
2633
Dan Albert8b72aef2015-03-23 19:13:21 -07002634class BlockDifference(object):
2635 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002636 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002637 self.tgt = tgt
2638 self.src = src
2639 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002640 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002641 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002642
Tao Baodd2a5892015-03-12 12:32:37 -07002643 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002644 version = max(
2645 int(i) for i in
2646 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002647 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002648 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002649
Tianjie Xu41976c72019-07-03 13:57:01 -07002650 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2651 version=self.version,
2652 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002653 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002654 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002655 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002656 self.touched_src_ranges = b.touched_src_ranges
2657 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002658
Yifan Hong10c530d2018-12-27 17:34:18 -08002659 # On devices with dynamic partitions, for new partitions,
2660 # src is None but OPTIONS.source_info_dict is not.
2661 if OPTIONS.source_info_dict is None:
2662 is_dynamic_build = OPTIONS.info_dict.get(
2663 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002664 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002665 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002666 is_dynamic_build = OPTIONS.source_info_dict.get(
2667 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002668 is_dynamic_source = partition in shlex.split(
2669 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002670
Yifan Hongbb2658d2019-01-25 12:30:58 -08002671 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002672 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2673
Yifan Hongbb2658d2019-01-25 12:30:58 -08002674 # For dynamic partitions builds, check partition list in both source
2675 # and target build because new partitions may be added, and existing
2676 # partitions may be removed.
2677 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2678
Yifan Hong10c530d2018-12-27 17:34:18 -08002679 if is_dynamic:
2680 self.device = 'map_partition("%s")' % partition
2681 else:
2682 if OPTIONS.source_info_dict is None:
2683 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2684 else:
2685 _, device_path = GetTypeAndDevice("/" + partition,
2686 OPTIONS.source_info_dict)
2687 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002688
Tao Baod8d14be2016-02-04 14:26:02 -08002689 @property
2690 def required_cache(self):
2691 return self._required_cache
2692
Tao Bao76def242017-11-21 09:25:31 -08002693 def WriteScript(self, script, output_zip, progress=None,
2694 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002695 if not self.src:
2696 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002697 script.Print("Patching %s image unconditionally..." % (self.partition,))
2698 else:
2699 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002700
Dan Albert8b72aef2015-03-23 19:13:21 -07002701 if progress:
2702 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002703 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002704
2705 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002706 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002707
Tao Bao9bc6bb22015-11-09 16:58:28 -08002708 def WriteStrictVerifyScript(self, script):
2709 """Verify all the blocks in the care_map, including clobbered blocks.
2710
2711 This differs from the WriteVerifyScript() function: a) it prints different
2712 error messages; b) it doesn't allow half-way updated images to pass the
2713 verification."""
2714
2715 partition = self.partition
2716 script.Print("Verifying %s..." % (partition,))
2717 ranges = self.tgt.care_map
2718 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002719 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002720 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2721 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002722 self.device, ranges_str,
2723 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002724 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002725 script.AppendExtra("")
2726
Tao Baod522bdc2016-04-12 15:53:16 -07002727 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002728 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002729
2730 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002731 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002732 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002733
2734 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002735 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002736 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002737 ranges = self.touched_src_ranges
2738 expected_sha1 = self.touched_src_sha1
2739 else:
2740 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2741 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002742
2743 # No blocks to be checked, skipping.
2744 if not ranges:
2745 return
2746
Tao Bao5ece99d2015-05-12 11:42:31 -07002747 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002748 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002749 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002750 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2751 '"%s.patch.dat")) then' % (
2752 self.device, ranges_str, expected_sha1,
2753 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002754 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002755 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002756
Tianjie Xufc3422a2015-12-15 11:53:59 -08002757 if self.version >= 4:
2758
2759 # Bug: 21124327
2760 # When generating incrementals for the system and vendor partitions in
2761 # version 4 or newer, explicitly check the first block (which contains
2762 # the superblock) of the partition to see if it's what we expect. If
2763 # this check fails, give an explicit log message about the partition
2764 # having been remounted R/W (the most likely explanation).
2765 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002766 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002767
2768 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002769 if partition == "system":
2770 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2771 else:
2772 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002773 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002774 'ifelse (block_image_recover({device}, "{ranges}") && '
2775 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002776 'package_extract_file("{partition}.transfer.list"), '
2777 '"{partition}.new.dat", "{partition}.patch.dat"), '
2778 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002779 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002780 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002781 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002782
Tao Baodd2a5892015-03-12 12:32:37 -07002783 # Abort the OTA update. Note that the incremental OTA cannot be applied
2784 # even if it may match the checksum of the target partition.
2785 # a) If version < 3, operations like move and erase will make changes
2786 # unconditionally and damage the partition.
2787 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002788 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002789 if partition == "system":
2790 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2791 else:
2792 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2793 script.AppendExtra((
2794 'abort("E%d: %s partition has unexpected contents");\n'
2795 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002796
Yifan Hong10c530d2018-12-27 17:34:18 -08002797 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002798 partition = self.partition
2799 script.Print('Verifying the updated %s image...' % (partition,))
2800 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2801 ranges = self.tgt.care_map
2802 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002803 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002804 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002805 self.device, ranges_str,
2806 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002807
2808 # Bug: 20881595
2809 # Verify that extended blocks are really zeroed out.
2810 if self.tgt.extended:
2811 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002812 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002813 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002814 self.device, ranges_str,
2815 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002816 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002817 if partition == "system":
2818 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2819 else:
2820 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002821 script.AppendExtra(
2822 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002823 ' abort("E%d: %s partition has unexpected non-zero contents after '
2824 'OTA update");\n'
2825 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002826 else:
2827 script.Print('Verified the updated %s image.' % (partition,))
2828
Tianjie Xu209db462016-05-24 17:34:52 -07002829 if partition == "system":
2830 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2831 else:
2832 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2833
Tao Bao5fcaaef2015-06-01 13:40:49 -07002834 script.AppendExtra(
2835 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002836 ' abort("E%d: %s partition has unexpected contents after OTA '
2837 'update");\n'
2838 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002839
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002840 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002841 ZipWrite(output_zip,
2842 '{}.transfer.list'.format(self.path),
2843 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002844
Tao Bao76def242017-11-21 09:25:31 -08002845 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2846 # its size. Quailty 9 almost triples the compression time but doesn't
2847 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002848 # zip | brotli(quality 6) | brotli(quality 9)
2849 # compressed_size: 942M | 869M (~8% reduced) | 854M
2850 # compression_time: 75s | 265s | 719s
2851 # decompression_time: 15s | 25s | 25s
2852
2853 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002854 brotli_cmd = ['brotli', '--quality=6',
2855 '--output={}.new.dat.br'.format(self.path),
2856 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002857 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002858 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002859
2860 new_data_name = '{}.new.dat.br'.format(self.partition)
2861 ZipWrite(output_zip,
2862 '{}.new.dat.br'.format(self.path),
2863 new_data_name,
2864 compress_type=zipfile.ZIP_STORED)
2865 else:
2866 new_data_name = '{}.new.dat'.format(self.partition)
2867 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2868
Dan Albert8e0178d2015-01-27 15:53:15 -08002869 ZipWrite(output_zip,
2870 '{}.patch.dat'.format(self.path),
2871 '{}.patch.dat'.format(self.partition),
2872 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002873
Tianjie Xu209db462016-05-24 17:34:52 -07002874 if self.partition == "system":
2875 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2876 else:
2877 code = ErrorCode.VENDOR_UPDATE_FAILURE
2878
Yifan Hong10c530d2018-12-27 17:34:18 -08002879 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002880 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002881 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002882 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002883 device=self.device, partition=self.partition,
2884 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002885 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002886
Dan Albert8b72aef2015-03-23 19:13:21 -07002887 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002888 data = source.ReadRangeSet(ranges)
2889 ctx = sha1()
2890
2891 for p in data:
2892 ctx.update(p)
2893
2894 return ctx.hexdigest()
2895
Tao Baoe9b61912015-07-09 17:37:49 -07002896 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2897 """Return the hash value for all zero blocks."""
2898 zero_block = '\x00' * 4096
2899 ctx = sha1()
2900 for _ in range(num_blocks):
2901 ctx.update(zero_block)
2902
2903 return ctx.hexdigest()
2904
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002905
Tianjie Xu41976c72019-07-03 13:57:01 -07002906# Expose these two classes to support vendor-specific scripts
2907DataImage = images.DataImage
2908EmptyImage = images.EmptyImage
2909
Tao Bao76def242017-11-21 09:25:31 -08002910
Doug Zongker96a57e72010-09-26 14:57:41 -07002911# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002912PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002913 "ext4": "EMMC",
2914 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002915 "f2fs": "EMMC",
2916 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002917}
Doug Zongker96a57e72010-09-26 14:57:41 -07002918
Tao Bao76def242017-11-21 09:25:31 -08002919
Doug Zongker96a57e72010-09-26 14:57:41 -07002920def GetTypeAndDevice(mount_point, info):
2921 fstab = info["fstab"]
2922 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002923 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2924 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002925 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002926 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002927
2928
2929def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002930 """Parses and converts a PEM-encoded certificate into DER-encoded.
2931
2932 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2933
2934 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002935 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002936 """
2937 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002938 save = False
2939 for line in data.split("\n"):
2940 if "--END CERTIFICATE--" in line:
2941 break
2942 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002943 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002944 if "--BEGIN CERTIFICATE--" in line:
2945 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002946 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002947 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002948
Tao Bao04e1f012018-02-04 12:13:35 -08002949
2950def ExtractPublicKey(cert):
2951 """Extracts the public key (PEM-encoded) from the given certificate file.
2952
2953 Args:
2954 cert: The certificate filename.
2955
2956 Returns:
2957 The public key string.
2958
2959 Raises:
2960 AssertionError: On non-zero return from 'openssl'.
2961 """
2962 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2963 # While openssl 1.1 writes the key into the given filename followed by '-out',
2964 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2965 # stdout instead.
2966 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2967 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2968 pubkey, stderrdata = proc.communicate()
2969 assert proc.returncode == 0, \
2970 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2971 return pubkey
2972
2973
Tao Bao1ac886e2019-06-26 11:58:22 -07002974def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002975 """Extracts the AVB public key from the given public or private key.
2976
2977 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002978 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002979 key: The input key file, which should be PEM-encoded public or private key.
2980
2981 Returns:
2982 The path to the extracted AVB public key file.
2983 """
2984 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2985 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002986 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002987 return output
2988
2989
Doug Zongker412c02f2014-02-13 10:58:24 -08002990def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2991 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002992 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002993
Tao Bao6d5d6232018-03-09 17:04:42 -08002994 Most of the space in the boot and recovery images is just the kernel, which is
2995 identical for the two, so the resulting patch should be efficient. Add it to
2996 the output zip, along with a shell script that is run from init.rc on first
2997 boot to actually do the patching and install the new recovery image.
2998
2999 Args:
3000 input_dir: The top-level input directory of the target-files.zip.
3001 output_sink: The callback function that writes the result.
3002 recovery_img: File object for the recovery image.
3003 boot_img: File objects for the boot image.
3004 info_dict: A dict returned by common.LoadInfoDict() on the input
3005 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003006 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003007 if info_dict is None:
3008 info_dict = OPTIONS.info_dict
3009
Tao Bao6d5d6232018-03-09 17:04:42 -08003010 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003011 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3012
3013 if board_uses_vendorimage:
3014 # In this case, the output sink is rooted at VENDOR
3015 recovery_img_path = "etc/recovery.img"
3016 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3017 sh_dir = "bin"
3018 else:
3019 # In this case the output sink is rooted at SYSTEM
3020 recovery_img_path = "vendor/etc/recovery.img"
3021 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3022 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003023
Tao Baof2cffbd2015-07-22 12:33:18 -07003024 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003025 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003026
3027 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003028 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003029 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003030 # With system-root-image, boot and recovery images will have mismatching
3031 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3032 # to handle such a case.
3033 if system_root_image:
3034 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003035 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003036 assert not os.path.exists(path)
3037 else:
3038 diff_program = ["imgdiff"]
3039 if os.path.exists(path):
3040 diff_program.append("-b")
3041 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003042 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003043 else:
3044 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003045
3046 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3047 _, _, patch = d.ComputePatch()
3048 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003049
Dan Albertebb19aa2015-03-27 19:11:53 -07003050 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003051 # The following GetTypeAndDevice()s need to use the path in the target
3052 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07003053 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
3054 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
3055 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003056 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003057
Tao Baof2cffbd2015-07-22 12:33:18 -07003058 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003059
3060 # Note that we use /vendor to refer to the recovery resources. This will
3061 # work for a separate vendor partition mounted at /vendor or a
3062 # /system/vendor subdirectory on the system partition, for which init will
3063 # create a symlink from /vendor to /system/vendor.
3064
3065 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003066if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3067 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003068 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003069 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3070 log -t recovery "Installing new recovery image: succeeded" || \\
3071 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003072else
3073 log -t recovery "Recovery image already installed"
3074fi
3075""" % {'type': recovery_type,
3076 'device': recovery_device,
3077 'sha1': recovery_img.sha1,
3078 'size': recovery_img.size}
3079 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003080 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003081if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3082 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003083 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003084 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3085 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3086 log -t recovery "Installing new recovery image: succeeded" || \\
3087 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003088else
3089 log -t recovery "Recovery image already installed"
3090fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003091""" % {'boot_size': boot_img.size,
3092 'boot_sha1': boot_img.sha1,
3093 'recovery_size': recovery_img.size,
3094 'recovery_sha1': recovery_img.sha1,
3095 'boot_type': boot_type,
3096 'boot_device': boot_device,
3097 'recovery_type': recovery_type,
3098 'recovery_device': recovery_device,
3099 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003100
Bill Peckhame868aec2019-09-17 17:06:47 -07003101 # The install script location moved from /system/etc to /system/bin in the L
3102 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3103 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003104
Tao Bao32fcdab2018-10-12 10:30:39 -07003105 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003106
Tao Baoda30cfa2017-12-01 16:19:46 -08003107 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003108
3109
3110class DynamicPartitionUpdate(object):
3111 def __init__(self, src_group=None, tgt_group=None, progress=None,
3112 block_difference=None):
3113 self.src_group = src_group
3114 self.tgt_group = tgt_group
3115 self.progress = progress
3116 self.block_difference = block_difference
3117
3118 @property
3119 def src_size(self):
3120 if not self.block_difference:
3121 return 0
3122 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3123
3124 @property
3125 def tgt_size(self):
3126 if not self.block_difference:
3127 return 0
3128 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3129
3130 @staticmethod
3131 def _GetSparseImageSize(img):
3132 if not img:
3133 return 0
3134 return img.blocksize * img.total_blocks
3135
3136
3137class DynamicGroupUpdate(object):
3138 def __init__(self, src_size=None, tgt_size=None):
3139 # None: group does not exist. 0: no size limits.
3140 self.src_size = src_size
3141 self.tgt_size = tgt_size
3142
3143
3144class DynamicPartitionsDifference(object):
3145 def __init__(self, info_dict, block_diffs, progress_dict=None,
3146 source_info_dict=None):
3147 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003148 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003149
3150 self._remove_all_before_apply = False
3151 if source_info_dict is None:
3152 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003153 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003154
Tao Baof1113e92019-06-18 12:10:14 -07003155 block_diff_dict = collections.OrderedDict(
3156 [(e.partition, e) for e in block_diffs])
3157
Yifan Hong10c530d2018-12-27 17:34:18 -08003158 assert len(block_diff_dict) == len(block_diffs), \
3159 "Duplicated BlockDifference object for {}".format(
3160 [partition for partition, count in
3161 collections.Counter(e.partition for e in block_diffs).items()
3162 if count > 1])
3163
Yifan Hong79997e52019-01-23 16:56:19 -08003164 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003165
3166 for p, block_diff in block_diff_dict.items():
3167 self._partition_updates[p] = DynamicPartitionUpdate()
3168 self._partition_updates[p].block_difference = block_diff
3169
3170 for p, progress in progress_dict.items():
3171 if p in self._partition_updates:
3172 self._partition_updates[p].progress = progress
3173
3174 tgt_groups = shlex.split(info_dict.get(
3175 "super_partition_groups", "").strip())
3176 src_groups = shlex.split(source_info_dict.get(
3177 "super_partition_groups", "").strip())
3178
3179 for g in tgt_groups:
3180 for p in shlex.split(info_dict.get(
3181 "super_%s_partition_list" % g, "").strip()):
3182 assert p in self._partition_updates, \
3183 "{} is in target super_{}_partition_list but no BlockDifference " \
3184 "object is provided.".format(p, g)
3185 self._partition_updates[p].tgt_group = g
3186
3187 for g in src_groups:
3188 for p in shlex.split(source_info_dict.get(
3189 "super_%s_partition_list" % g, "").strip()):
3190 assert p in self._partition_updates, \
3191 "{} is in source super_{}_partition_list but no BlockDifference " \
3192 "object is provided.".format(p, g)
3193 self._partition_updates[p].src_group = g
3194
Yifan Hong45433e42019-01-18 13:55:25 -08003195 target_dynamic_partitions = set(shlex.split(info_dict.get(
3196 "dynamic_partition_list", "").strip()))
3197 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3198 if u.tgt_size)
3199 assert block_diffs_with_target == target_dynamic_partitions, \
3200 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3201 list(target_dynamic_partitions), list(block_diffs_with_target))
3202
3203 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3204 "dynamic_partition_list", "").strip()))
3205 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3206 if u.src_size)
3207 assert block_diffs_with_source == source_dynamic_partitions, \
3208 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3209 list(source_dynamic_partitions), list(block_diffs_with_source))
3210
Yifan Hong10c530d2018-12-27 17:34:18 -08003211 if self._partition_updates:
3212 logger.info("Updating dynamic partitions %s",
3213 self._partition_updates.keys())
3214
Yifan Hong79997e52019-01-23 16:56:19 -08003215 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003216
3217 for g in tgt_groups:
3218 self._group_updates[g] = DynamicGroupUpdate()
3219 self._group_updates[g].tgt_size = int(info_dict.get(
3220 "super_%s_group_size" % g, "0").strip())
3221
3222 for g in src_groups:
3223 if g not in self._group_updates:
3224 self._group_updates[g] = DynamicGroupUpdate()
3225 self._group_updates[g].src_size = int(source_info_dict.get(
3226 "super_%s_group_size" % g, "0").strip())
3227
3228 self._Compute()
3229
3230 def WriteScript(self, script, output_zip, write_verify_script=False):
3231 script.Comment('--- Start patching dynamic partitions ---')
3232 for p, u in self._partition_updates.items():
3233 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3234 script.Comment('Patch partition %s' % p)
3235 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3236 write_verify_script=False)
3237
3238 op_list_path = MakeTempFile()
3239 with open(op_list_path, 'w') as f:
3240 for line in self._op_list:
3241 f.write('{}\n'.format(line))
3242
3243 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3244
3245 script.Comment('Update dynamic partition metadata')
3246 script.AppendExtra('assert(update_dynamic_partitions('
3247 'package_extract_file("dynamic_partitions_op_list")));')
3248
3249 if write_verify_script:
3250 for p, u in self._partition_updates.items():
3251 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3252 u.block_difference.WritePostInstallVerifyScript(script)
3253 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3254
3255 for p, u in self._partition_updates.items():
3256 if u.tgt_size and u.src_size <= u.tgt_size:
3257 script.Comment('Patch partition %s' % p)
3258 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3259 write_verify_script=write_verify_script)
3260 if write_verify_script:
3261 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3262
3263 script.Comment('--- End patching dynamic partitions ---')
3264
3265 def _Compute(self):
3266 self._op_list = list()
3267
3268 def append(line):
3269 self._op_list.append(line)
3270
3271 def comment(line):
3272 self._op_list.append("# %s" % line)
3273
3274 if self._remove_all_before_apply:
3275 comment('Remove all existing dynamic partitions and groups before '
3276 'applying full OTA')
3277 append('remove_all_groups')
3278
3279 for p, u in self._partition_updates.items():
3280 if u.src_group and not u.tgt_group:
3281 append('remove %s' % p)
3282
3283 for p, u in self._partition_updates.items():
3284 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3285 comment('Move partition %s from %s to default' % (p, u.src_group))
3286 append('move %s default' % p)
3287
3288 for p, u in self._partition_updates.items():
3289 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3290 comment('Shrink partition %s from %d to %d' %
3291 (p, u.src_size, u.tgt_size))
3292 append('resize %s %s' % (p, u.tgt_size))
3293
3294 for g, u in self._group_updates.items():
3295 if u.src_size is not None and u.tgt_size is None:
3296 append('remove_group %s' % g)
3297 if (u.src_size is not None and u.tgt_size is not None and
3298 u.src_size > u.tgt_size):
3299 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3300 append('resize_group %s %d' % (g, u.tgt_size))
3301
3302 for g, u in self._group_updates.items():
3303 if u.src_size is None and u.tgt_size is not None:
3304 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3305 append('add_group %s %d' % (g, u.tgt_size))
3306 if (u.src_size is not None and u.tgt_size is not None and
3307 u.src_size < u.tgt_size):
3308 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3309 append('resize_group %s %d' % (g, u.tgt_size))
3310
3311 for p, u in self._partition_updates.items():
3312 if u.tgt_group and not u.src_group:
3313 comment('Add partition %s to group %s' % (p, u.tgt_group))
3314 append('add %s %s' % (p, u.tgt_group))
3315
3316 for p, u in self._partition_updates.items():
3317 if u.tgt_size and u.src_size < u.tgt_size:
3318 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3319 append('resize %s %d' % (p, u.tgt_size))
3320
3321 for p, u in self._partition_updates.items():
3322 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3323 comment('Move partition %s from default to %s' %
3324 (p, u.tgt_group))
3325 append('move %s %s' % (p, u.tgt_group))