blob: 93e14e5c17612484ded8b810f5536a7f3ff80356 [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):
Yifan Hong65afc072020-04-17 10:08:10 -0700851 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
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
Yifan Hong65afc072020-04-17 10:08:10 -0700857 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700858
859 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800860 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700861 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700862 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700863 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700864
Tao Baod1de6f32017-03-01 16:38:48 -0800865 assert fstab_version == 2
866
867 d = {}
868 for line in data.split("\n"):
869 line = line.strip()
870 if not line or line.startswith("#"):
871 continue
872
873 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
874 pieces = line.split()
875 if len(pieces) != 5:
876 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
877
878 # Ignore entries that are managed by vold.
879 options = pieces[4]
880 if "voldmanaged=" in options:
881 continue
882
883 # It's a good line, parse it.
884 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700885 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800886 options = options.split(",")
887 for i in options:
888 if i.startswith("length="):
889 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700890 elif i == "slotselect":
891 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800892 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800893 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800895
Tao Baod1de6f32017-03-01 16:38:48 -0800896 mount_flags = pieces[3]
897 # Honor the SELinux context if present.
898 context = None
899 for i in mount_flags.split(","):
900 if i.startswith("context="):
901 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800902
Tao Baod1de6f32017-03-01 16:38:48 -0800903 mount_point = pieces[1]
904 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700905 device=pieces[0], length=length, context=context,
906 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800907
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700908 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700909 # system. Other areas assume system is always at "/system" so point /system
910 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700911 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800912 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700913 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700914 return d
915
916
Tao Bao765668f2019-10-04 22:03:00 -0700917def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
918 """Finds the path to recovery fstab and loads its contents."""
919 # recovery fstab is only meaningful when installing an update via recovery
920 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700921 if info_dict.get('ab_update') == 'true' and \
922 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700923 return None
924
925 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
926 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
927 # cases, since it may load the info_dict from an old build (e.g. when
928 # generating incremental OTAs from that build).
929 system_root_image = info_dict.get('system_root_image') == 'true'
930 if info_dict.get('no_recovery') != 'true':
931 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
932 if isinstance(input_file, zipfile.ZipFile):
933 if recovery_fstab_path not in input_file.namelist():
934 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
935 else:
936 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
937 if not os.path.exists(path):
938 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
939 return LoadRecoveryFSTab(
940 read_helper, info_dict['fstab_version'], recovery_fstab_path,
941 system_root_image)
942
943 if info_dict.get('recovery_as_boot') == 'true':
944 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
945 if isinstance(input_file, zipfile.ZipFile):
946 if recovery_fstab_path not in input_file.namelist():
947 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
948 else:
949 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
950 if not os.path.exists(path):
951 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
952 return LoadRecoveryFSTab(
953 read_helper, info_dict['fstab_version'], recovery_fstab_path,
954 system_root_image)
955
956 return None
957
958
Doug Zongker37974732010-09-16 17:44:38 -0700959def DumpInfoDict(d):
960 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700961 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700962
Dan Albert8b72aef2015-03-23 19:13:21 -0700963
Daniel Norman55417142019-11-25 16:04:36 -0800964def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700965 """Merges dynamic partition info variables.
966
967 Args:
968 framework_dict: The dictionary of dynamic partition info variables from the
969 partial framework target files.
970 vendor_dict: The dictionary of dynamic partition info variables from the
971 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700972
973 Returns:
974 The merged dynamic partition info dictionary.
975 """
976 merged_dict = {}
977 # Partition groups and group sizes are defined by the vendor dict because
978 # these values may vary for each board that uses a shared system image.
979 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800980 framework_dynamic_partition_list = framework_dict.get(
981 "dynamic_partition_list", "")
982 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
983 merged_dict["dynamic_partition_list"] = ("%s %s" % (
984 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700985 for partition_group in merged_dict["super_partition_groups"].split(" "):
986 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800987 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700988 if key not in vendor_dict:
989 raise ValueError("Vendor dict does not contain required key %s." % key)
990 merged_dict[key] = vendor_dict[key]
991
992 # Set the partition group's partition list using a concatenation of the
993 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800994 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700995 merged_dict[key] = (
996 "%s %s" %
997 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530998
999 # Pick virtual ab related flags from vendor dict, if defined.
1000 if "virtual_ab" in vendor_dict.keys():
1001 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
1002 if "virtual_ab_retrofit" in vendor_dict.keys():
1003 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001004 return merged_dict
1005
1006
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001007def AppendAVBSigningArgs(cmd, partition):
1008 """Append signing arguments for avbtool."""
1009 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1010 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001011 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1012 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1013 if os.path.exists(new_key_path):
1014 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001015 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1016 if key_path and algorithm:
1017 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001018 avb_salt = OPTIONS.info_dict.get("avb_salt")
1019 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001020 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001021 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001022
1023
Tao Bao765668f2019-10-04 22:03:00 -07001024def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001025 """Returns the VBMeta arguments for partition.
1026
1027 It sets up the VBMeta argument by including the partition descriptor from the
1028 given 'image', or by configuring the partition as a chained partition.
1029
1030 Args:
1031 partition: The name of the partition (e.g. "system").
1032 image: The path to the partition image.
1033 info_dict: A dict returned by common.LoadInfoDict(). Will use
1034 OPTIONS.info_dict if None has been given.
1035
1036 Returns:
1037 A list of VBMeta arguments.
1038 """
1039 if info_dict is None:
1040 info_dict = OPTIONS.info_dict
1041
1042 # Check if chain partition is used.
1043 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001044 if not key_path:
1045 return ["--include_descriptors_from_image", image]
1046
1047 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1048 # into vbmeta.img. The recovery image will be configured on an independent
1049 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1050 # See details at
1051 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001052 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001053 return []
1054
1055 # Otherwise chain the partition into vbmeta.
1056 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1057 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001058
1059
Tao Bao02a08592018-07-22 12:40:45 -07001060def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1061 """Constructs and returns the arg to build or verify a chained partition.
1062
1063 Args:
1064 partition: The partition name.
1065 info_dict: The info dict to look up the key info and rollback index
1066 location.
1067 key: The key to be used for building or verifying the partition. Defaults to
1068 the key listed in info_dict.
1069
1070 Returns:
1071 A string of form "partition:rollback_index_location:key" that can be used to
1072 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001073 """
1074 if key is None:
1075 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001076 if key and not os.path.exists(key) and OPTIONS.search_path:
1077 new_key_path = os.path.join(OPTIONS.search_path, key)
1078 if os.path.exists(new_key_path):
1079 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001080 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001081 rollback_index_location = info_dict[
1082 "avb_" + partition + "_rollback_index_location"]
1083 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1084
1085
Tianjie20dd8f22020-04-19 15:51:16 -07001086def ConstructAftlMakeImageCommands(output_image):
1087 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001088
1089 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001090 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001091 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1092 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1093 'No AFTL manufacturer key provided.'
1094
1095 vbmeta_image = MakeTempFile()
1096 os.rename(output_image, vbmeta_image)
1097 build_info = BuildInfo(OPTIONS.info_dict)
1098 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001099 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001100 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001101 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001102 "--vbmeta_image_path", vbmeta_image,
1103 "--output", output_image,
1104 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001105 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001106 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1107 "--algorithm", "SHA256_RSA4096",
1108 "--padding", "4096"]
1109 if OPTIONS.aftl_signer_helper:
1110 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001111 return aftl_cmd
1112
1113
1114def AddAftlInclusionProof(output_image):
1115 """Appends the aftl inclusion proof to the vbmeta image."""
1116
1117 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001118 RunAndCheckOutput(aftl_cmd)
1119
1120 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1121 output_image, '--transparency_log_pub_keys',
1122 OPTIONS.aftl_key_path]
1123 RunAndCheckOutput(verify_cmd)
1124
1125
Daniel Norman276f0622019-07-26 14:13:51 -07001126def BuildVBMeta(image_path, partitions, name, needed_partitions):
1127 """Creates a VBMeta image.
1128
1129 It generates the requested VBMeta image. The requested image could be for
1130 top-level or chained VBMeta image, which is determined based on the name.
1131
1132 Args:
1133 image_path: The output path for the new VBMeta image.
1134 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001135 values. Only valid partition names are accepted, as partitions listed
1136 in common.AVB_PARTITIONS and custom partitions listed in
1137 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001138 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1139 needed_partitions: Partitions whose descriptors should be included into the
1140 generated VBMeta image.
1141
1142 Raises:
1143 AssertionError: On invalid input args.
1144 """
1145 avbtool = OPTIONS.info_dict["avb_avbtool"]
1146 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1147 AppendAVBSigningArgs(cmd, name)
1148
Hongguang Chenf23364d2020-04-27 18:36:36 -07001149 custom_partitions = OPTIONS.info_dict.get(
1150 "avb_custom_images_partition_list", "").strip().split()
1151
Daniel Norman276f0622019-07-26 14:13:51 -07001152 for partition, path in partitions.items():
1153 if partition not in needed_partitions:
1154 continue
1155 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001156 partition in AVB_VBMETA_PARTITIONS or
1157 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001158 'Unknown partition: {}'.format(partition)
1159 assert os.path.exists(path), \
1160 'Failed to find {} for {}'.format(path, partition)
1161 cmd.extend(GetAvbPartitionArg(partition, path))
1162
1163 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1164 if args and args.strip():
1165 split_args = shlex.split(args)
1166 for index, arg in enumerate(split_args[:-1]):
1167 # Sanity check that the image file exists. Some images might be defined
1168 # as a path relative to source tree, which may not be available at the
1169 # same location when running this script (we have the input target_files
1170 # zip only). For such cases, we additionally scan other locations (e.g.
1171 # IMAGES/, RADIO/, etc) before bailing out.
1172 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001173 chained_image = split_args[index + 1]
1174 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001175 continue
1176 found = False
1177 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1178 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001179 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001180 if os.path.exists(alt_path):
1181 split_args[index + 1] = alt_path
1182 found = True
1183 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001184 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001185 cmd.extend(split_args)
1186
1187 RunAndCheckOutput(cmd)
1188
Tianjie Xueaed60c2020-03-12 00:33:28 -07001189 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001190 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001191 AddAftlInclusionProof(image_path)
1192
Daniel Norman276f0622019-07-26 14:13:51 -07001193
Steve Mucklee1b10862019-07-10 10:49:37 -07001194def _MakeRamdisk(sourcedir, fs_config_file=None):
1195 ramdisk_img = tempfile.NamedTemporaryFile()
1196
1197 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1198 cmd = ["mkbootfs", "-f", fs_config_file,
1199 os.path.join(sourcedir, "RAMDISK")]
1200 else:
1201 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1202 p1 = Run(cmd, stdout=subprocess.PIPE)
1203 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1204
1205 p2.wait()
1206 p1.wait()
1207 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1208 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1209
1210 return ramdisk_img
1211
1212
Steve Muckle9793cf62020-04-08 18:27:00 -07001213def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001214 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001215 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001216
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001217 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001218 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1219 we are building a two-step special image (i.e. building a recovery image to
1220 be loaded into /boot in two-step OTAs).
1221
1222 Return the image data, or None if sourcedir does not appear to contains files
1223 for building the requested image.
1224 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001225
Steve Muckle9793cf62020-04-08 18:27:00 -07001226 # "boot" or "recovery", without extension.
1227 partition_name = os.path.basename(sourcedir).lower()
1228
1229 if partition_name == "recovery":
1230 kernel = "kernel"
1231 else:
1232 kernel = image_name.replace("boot", "kernel")
1233 kernel = kernel.replace(".img","")
1234 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001235 return None
1236
1237 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001238 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001239
Doug Zongkerd5131602012-08-02 14:46:42 -07001240 if info_dict is None:
1241 info_dict = OPTIONS.info_dict
1242
Doug Zongkereef39442009-04-02 12:14:19 -07001243 img = tempfile.NamedTemporaryFile()
1244
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001245 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001246 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001247
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001248 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1249 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1250
Steve Muckle9793cf62020-04-08 18:27:00 -07001251 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001252
Benoit Fradina45a8682014-07-14 21:00:43 +02001253 fn = os.path.join(sourcedir, "second")
1254 if os.access(fn, os.F_OK):
1255 cmd.append("--second")
1256 cmd.append(fn)
1257
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001258 fn = os.path.join(sourcedir, "dtb")
1259 if os.access(fn, os.F_OK):
1260 cmd.append("--dtb")
1261 cmd.append(fn)
1262
Doug Zongker171f1cd2009-06-15 22:36:37 -07001263 fn = os.path.join(sourcedir, "cmdline")
1264 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001265 cmd.append("--cmdline")
1266 cmd.append(open(fn).read().rstrip("\n"))
1267
1268 fn = os.path.join(sourcedir, "base")
1269 if os.access(fn, os.F_OK):
1270 cmd.append("--base")
1271 cmd.append(open(fn).read().rstrip("\n"))
1272
Ying Wang4de6b5b2010-08-25 14:29:34 -07001273 fn = os.path.join(sourcedir, "pagesize")
1274 if os.access(fn, os.F_OK):
1275 cmd.append("--pagesize")
1276 cmd.append(open(fn).read().rstrip("\n"))
1277
Steve Mucklef84668e2020-03-16 19:13:46 -07001278 if partition_name == "recovery":
1279 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301280 if not args:
1281 # Fall back to "mkbootimg_args" for recovery image
1282 # in case "recovery_mkbootimg_args" is not set.
1283 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001284 else:
1285 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001286 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001287 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001288
Tao Bao76def242017-11-21 09:25:31 -08001289 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001290 if args and args.strip():
1291 cmd.extend(shlex.split(args))
1292
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001293 if has_ramdisk:
1294 cmd.extend(["--ramdisk", ramdisk_img.name])
1295
Tao Baod95e9fd2015-03-29 23:07:41 -07001296 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001297 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001298 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001299 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001300 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001301 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001302
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001303 if partition_name == "recovery":
1304 if info_dict.get("include_recovery_dtbo") == "true":
1305 fn = os.path.join(sourcedir, "recovery_dtbo")
1306 cmd.extend(["--recovery_dtbo", fn])
1307 if info_dict.get("include_recovery_acpio") == "true":
1308 fn = os.path.join(sourcedir, "recovery_acpio")
1309 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001310
Tao Bao986ee862018-10-04 15:46:16 -07001311 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001312
Tao Bao76def242017-11-21 09:25:31 -08001313 if (info_dict.get("boot_signer") == "true" and
1314 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001315 # Hard-code the path as "/boot" for two-step special recovery image (which
1316 # will be loaded into /boot during the two-step OTA).
1317 if two_step_image:
1318 path = "/boot"
1319 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001320 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001321 cmd = [OPTIONS.boot_signer_path]
1322 cmd.extend(OPTIONS.boot_signer_args)
1323 cmd.extend([path, img.name,
1324 info_dict["verity_key"] + ".pk8",
1325 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001326 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001327
Tao Baod95e9fd2015-03-29 23:07:41 -07001328 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001329 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001330 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001331 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001332 # We have switched from the prebuilt futility binary to using the tool
1333 # (futility-host) built from the source. Override the setting in the old
1334 # TF.zip.
1335 futility = info_dict["futility"]
1336 if futility.startswith("prebuilts/"):
1337 futility = "futility-host"
1338 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001339 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001340 info_dict["vboot_key"] + ".vbprivk",
1341 info_dict["vboot_subkey"] + ".vbprivk",
1342 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001343 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001344 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001345
Tao Baof3282b42015-04-01 11:21:55 -07001346 # Clean up the temp files.
1347 img_unsigned.close()
1348 img_keyblock.close()
1349
David Zeuthen8fecb282017-12-01 16:24:01 -05001350 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001351 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001352 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001353 if partition_name == "recovery":
1354 part_size = info_dict["recovery_size"]
1355 else:
1356 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001357 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001358 "--partition_size", str(part_size), "--partition_name",
1359 partition_name]
1360 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001361 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001362 if args and args.strip():
1363 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001364 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001365
1366 img.seek(os.SEEK_SET, 0)
1367 data = img.read()
1368
1369 if has_ramdisk:
1370 ramdisk_img.close()
1371 img.close()
1372
1373 return data
1374
1375
Doug Zongkerd5131602012-08-02 14:46:42 -07001376def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001377 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001378 """Return a File object with the desired bootable image.
1379
1380 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1381 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1382 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001383
Doug Zongker55d93282011-01-25 17:03:34 -08001384 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1385 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001386 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001387 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001388
1389 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1390 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001391 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001392 return File.FromLocalFile(name, prebuilt_path)
1393
Tao Bao32fcdab2018-10-12 10:30:39 -07001394 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001395
1396 if info_dict is None:
1397 info_dict = OPTIONS.info_dict
1398
1399 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001400 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1401 # for recovery.
1402 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1403 prebuilt_name != "boot.img" or
1404 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001405
Doug Zongker6f1d0312014-08-22 08:07:12 -07001406 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001407 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001408 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001409 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001410 if data:
1411 return File(name, data)
1412 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001413
Doug Zongkereef39442009-04-02 12:14:19 -07001414
Steve Mucklee1b10862019-07-10 10:49:37 -07001415def _BuildVendorBootImage(sourcedir, info_dict=None):
1416 """Build a vendor boot image from the specified sourcedir.
1417
1418 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1419 turn them into a vendor boot image.
1420
1421 Return the image data, or None if sourcedir does not appear to contains files
1422 for building the requested image.
1423 """
1424
1425 if info_dict is None:
1426 info_dict = OPTIONS.info_dict
1427
1428 img = tempfile.NamedTemporaryFile()
1429
1430 ramdisk_img = _MakeRamdisk(sourcedir)
1431
1432 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1433 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1434
1435 cmd = [mkbootimg]
1436
1437 fn = os.path.join(sourcedir, "dtb")
1438 if os.access(fn, os.F_OK):
1439 cmd.append("--dtb")
1440 cmd.append(fn)
1441
1442 fn = os.path.join(sourcedir, "vendor_cmdline")
1443 if os.access(fn, os.F_OK):
1444 cmd.append("--vendor_cmdline")
1445 cmd.append(open(fn).read().rstrip("\n"))
1446
1447 fn = os.path.join(sourcedir, "base")
1448 if os.access(fn, os.F_OK):
1449 cmd.append("--base")
1450 cmd.append(open(fn).read().rstrip("\n"))
1451
1452 fn = os.path.join(sourcedir, "pagesize")
1453 if os.access(fn, os.F_OK):
1454 cmd.append("--pagesize")
1455 cmd.append(open(fn).read().rstrip("\n"))
1456
1457 args = info_dict.get("mkbootimg_args")
1458 if args and args.strip():
1459 cmd.extend(shlex.split(args))
1460
1461 args = info_dict.get("mkbootimg_version_args")
1462 if args and args.strip():
1463 cmd.extend(shlex.split(args))
1464
1465 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1466 cmd.extend(["--vendor_boot", img.name])
1467
1468 RunAndCheckOutput(cmd)
1469
1470 # AVB: if enabled, calculate and add hash.
1471 if info_dict.get("avb_enable") == "true":
1472 avbtool = info_dict["avb_avbtool"]
1473 part_size = info_dict["vendor_boot_size"]
1474 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001475 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001476 AppendAVBSigningArgs(cmd, "vendor_boot")
1477 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1478 if args and args.strip():
1479 cmd.extend(shlex.split(args))
1480 RunAndCheckOutput(cmd)
1481
1482 img.seek(os.SEEK_SET, 0)
1483 data = img.read()
1484
1485 ramdisk_img.close()
1486 img.close()
1487
1488 return data
1489
1490
1491def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1492 info_dict=None):
1493 """Return a File object with the desired vendor boot image.
1494
1495 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1496 the source files in 'unpack_dir'/'tree_subdir'."""
1497
1498 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1499 if os.path.exists(prebuilt_path):
1500 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1501 return File.FromLocalFile(name, prebuilt_path)
1502
1503 logger.info("building image from target_files %s...", tree_subdir)
1504
1505 if info_dict is None:
1506 info_dict = OPTIONS.info_dict
1507
1508 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1509 if data:
1510 return File(name, data)
1511 return None
1512
1513
Narayan Kamatha07bf042017-08-14 14:49:21 +01001514def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001515 """Gunzips the given gzip compressed file to a given output file."""
1516 with gzip.open(in_filename, "rb") as in_file, \
1517 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001518 shutil.copyfileobj(in_file, out_file)
1519
1520
Tao Bao0ff15de2019-03-20 11:26:06 -07001521def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001522 """Unzips the archive to the given directory.
1523
1524 Args:
1525 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001526 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001527 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1528 archvie. Non-matching patterns will be filtered out. If there's no match
1529 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001530 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001531 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001532 if patterns is not None:
1533 # Filter out non-matching patterns. unzip will complain otherwise.
1534 with zipfile.ZipFile(filename) as input_zip:
1535 names = input_zip.namelist()
1536 filtered = [
1537 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1538
1539 # There isn't any matching files. Don't unzip anything.
1540 if not filtered:
1541 return
1542 cmd.extend(filtered)
1543
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001544 RunAndCheckOutput(cmd)
1545
1546
Doug Zongker75f17362009-12-08 13:46:44 -08001547def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001548 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001549
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001550 Args:
1551 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1552 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1553
1554 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1555 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001556
Tao Bao1c830bf2017-12-25 10:43:47 -08001557 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001558 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001559 """
Doug Zongkereef39442009-04-02 12:14:19 -07001560
Tao Bao1c830bf2017-12-25 10:43:47 -08001561 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001562 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1563 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001564 UnzipToDir(m.group(1), tmp, pattern)
1565 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001566 filename = m.group(1)
1567 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001568 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001569
Tao Baodba59ee2018-01-09 13:21:02 -08001570 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001571
1572
Yifan Hong8a66a712019-04-04 15:37:57 -07001573def GetUserImage(which, tmpdir, input_zip,
1574 info_dict=None,
1575 allow_shared_blocks=None,
1576 hashtree_info_generator=None,
1577 reset_file_map=False):
1578 """Returns an Image object suitable for passing to BlockImageDiff.
1579
1580 This function loads the specified image from the given path. If the specified
1581 image is sparse, it also performs additional processing for OTA purpose. For
1582 example, it always adds block 0 to clobbered blocks list. It also detects
1583 files that cannot be reconstructed from the block list, for whom we should
1584 avoid applying imgdiff.
1585
1586 Args:
1587 which: The partition name.
1588 tmpdir: The directory that contains the prebuilt image and block map file.
1589 input_zip: The target-files ZIP archive.
1590 info_dict: The dict to be looked up for relevant info.
1591 allow_shared_blocks: If image is sparse, whether having shared blocks is
1592 allowed. If none, it is looked up from info_dict.
1593 hashtree_info_generator: If present and image is sparse, generates the
1594 hashtree_info for this sparse image.
1595 reset_file_map: If true and image is sparse, reset file map before returning
1596 the image.
1597 Returns:
1598 A Image object. If it is a sparse image and reset_file_map is False, the
1599 image will have file_map info loaded.
1600 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001601 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001602 info_dict = LoadInfoDict(input_zip)
1603
1604 is_sparse = info_dict.get("extfs_sparse_flag")
1605
1606 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1607 # shared blocks (i.e. some blocks will show up in multiple files' block
1608 # list). We can only allocate such shared blocks to the first "owner", and
1609 # disable imgdiff for all later occurrences.
1610 if allow_shared_blocks is None:
1611 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1612
1613 if is_sparse:
1614 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1615 hashtree_info_generator)
1616 if reset_file_map:
1617 img.ResetFileMap()
1618 return img
1619 else:
1620 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1621
1622
1623def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1624 """Returns a Image object suitable for passing to BlockImageDiff.
1625
1626 This function loads the specified non-sparse image from the given path.
1627
1628 Args:
1629 which: The partition name.
1630 tmpdir: The directory that contains the prebuilt image and block map file.
1631 Returns:
1632 A Image object.
1633 """
1634 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1635 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1636
1637 # The image and map files must have been created prior to calling
1638 # ota_from_target_files.py (since LMP).
1639 assert os.path.exists(path) and os.path.exists(mappath)
1640
Tianjie Xu41976c72019-07-03 13:57:01 -07001641 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1642
Yifan Hong8a66a712019-04-04 15:37:57 -07001643
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001644def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1645 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001646 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1647
1648 This function loads the specified sparse image from the given path, and
1649 performs additional processing for OTA purpose. For example, it always adds
1650 block 0 to clobbered blocks list. It also detects files that cannot be
1651 reconstructed from the block list, for whom we should avoid applying imgdiff.
1652
1653 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001654 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001655 tmpdir: The directory that contains the prebuilt image and block map file.
1656 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001657 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001658 hashtree_info_generator: If present, generates the hashtree_info for this
1659 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001660 Returns:
1661 A SparseImage object, with file_map info loaded.
1662 """
Tao Baoc765cca2018-01-31 17:32:40 -08001663 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1664 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1665
1666 # The image and map files must have been created prior to calling
1667 # ota_from_target_files.py (since LMP).
1668 assert os.path.exists(path) and os.path.exists(mappath)
1669
1670 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1671 # it to clobbered_blocks so that it will be written to the target
1672 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1673 clobbered_blocks = "0"
1674
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001675 image = sparse_img.SparseImage(
1676 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1677 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001678
1679 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1680 # if they contain all zeros. We can't reconstruct such a file from its block
1681 # list. Tag such entries accordingly. (Bug: 65213616)
1682 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001683 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001684 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001685 continue
1686
Tom Cherryd14b8952018-08-09 14:26:00 -07001687 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1688 # filename listed in system.map may contain an additional leading slash
1689 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1690 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001691 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001692
Tom Cherryd14b8952018-08-09 14:26:00 -07001693 # Special handling another case, where files not under /system
1694 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001695 if which == 'system' and not arcname.startswith('SYSTEM'):
1696 arcname = 'ROOT/' + arcname
1697
1698 assert arcname in input_zip.namelist(), \
1699 "Failed to find the ZIP entry for {}".format(entry)
1700
Tao Baoc765cca2018-01-31 17:32:40 -08001701 info = input_zip.getinfo(arcname)
1702 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001703
1704 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001705 # image, check the original block list to determine its completeness. Note
1706 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001707 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001708 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001709
Tao Baoc765cca2018-01-31 17:32:40 -08001710 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1711 ranges.extra['incomplete'] = True
1712
1713 return image
1714
1715
Doug Zongkereef39442009-04-02 12:14:19 -07001716def GetKeyPasswords(keylist):
1717 """Given a list of keys, prompt the user to enter passwords for
1718 those which require them. Return a {key: password} dict. password
1719 will be None if the key has no password."""
1720
Doug Zongker8ce7c252009-05-22 13:34:54 -07001721 no_passwords = []
1722 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001723 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001724 devnull = open("/dev/null", "w+b")
1725 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001726 # We don't need a password for things that aren't really keys.
1727 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001728 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001729 continue
1730
T.R. Fullhart37e10522013-03-18 10:31:26 -07001731 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001732 "-inform", "DER", "-nocrypt"],
1733 stdin=devnull.fileno(),
1734 stdout=devnull.fileno(),
1735 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001736 p.communicate()
1737 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001738 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001739 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001740 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001741 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1742 "-inform", "DER", "-passin", "pass:"],
1743 stdin=devnull.fileno(),
1744 stdout=devnull.fileno(),
1745 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001746 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001747 if p.returncode == 0:
1748 # Encrypted key with empty string as password.
1749 key_passwords[k] = ''
1750 elif stderr.startswith('Error decrypting key'):
1751 # Definitely encrypted key.
1752 # It would have said "Error reading key" if it didn't parse correctly.
1753 need_passwords.append(k)
1754 else:
1755 # Potentially, a type of key that openssl doesn't understand.
1756 # We'll let the routines in signapk.jar handle it.
1757 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001758 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001759
T.R. Fullhart37e10522013-03-18 10:31:26 -07001760 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001761 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001762 return key_passwords
1763
1764
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001765def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001766 """Gets the minSdkVersion declared in the APK.
1767
changho.shin0f125362019-07-08 10:59:00 +09001768 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001769 This can be both a decimal number (API Level) or a codename.
1770
1771 Args:
1772 apk_name: The APK filename.
1773
1774 Returns:
1775 The parsed SDK version string.
1776
1777 Raises:
1778 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001779 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001780 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001781 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001782 stderr=subprocess.PIPE)
1783 stdoutdata, stderrdata = proc.communicate()
1784 if proc.returncode != 0:
1785 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001786 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001787 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001788
Tao Baof47bf0f2018-03-21 23:28:51 -07001789 for line in stdoutdata.split("\n"):
1790 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001791 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1792 if m:
1793 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001794 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001795
1796
1797def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001798 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001799
Tao Baof47bf0f2018-03-21 23:28:51 -07001800 If minSdkVersion is set to a codename, it is translated to a number using the
1801 provided map.
1802
1803 Args:
1804 apk_name: The APK filename.
1805
1806 Returns:
1807 The parsed SDK version number.
1808
1809 Raises:
1810 ExternalError: On failing to get the min SDK version number.
1811 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001812 version = GetMinSdkVersion(apk_name)
1813 try:
1814 return int(version)
1815 except ValueError:
1816 # Not a decimal number. Codename?
1817 if version in codename_to_api_level_map:
1818 return codename_to_api_level_map[version]
1819 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001820 raise ExternalError(
1821 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1822 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001823
1824
1825def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001826 codename_to_api_level_map=None, whole_file=False,
1827 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001828 """Sign the input_name zip/jar/apk, producing output_name. Use the
1829 given key and password (the latter may be None if the key does not
1830 have a password.
1831
Doug Zongker951495f2009-08-14 12:44:19 -07001832 If whole_file is true, use the "-w" option to SignApk to embed a
1833 signature that covers the whole file in the archive comment of the
1834 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001835
1836 min_api_level is the API Level (int) of the oldest platform this file may end
1837 up on. If not specified for an APK, the API Level is obtained by interpreting
1838 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1839
1840 codename_to_api_level_map is needed to translate the codename which may be
1841 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001842
1843 Caller may optionally specify extra args to be passed to SignApk, which
1844 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001845 """
Tao Bao76def242017-11-21 09:25:31 -08001846 if codename_to_api_level_map is None:
1847 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001848 if extra_signapk_args is None:
1849 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001850
Alex Klyubin9667b182015-12-10 13:38:50 -08001851 java_library_path = os.path.join(
1852 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1853
Tao Baoe95540e2016-11-08 12:08:53 -08001854 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1855 ["-Djava.library.path=" + java_library_path,
1856 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001857 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001858 if whole_file:
1859 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001860
1861 min_sdk_version = min_api_level
1862 if min_sdk_version is None:
1863 if not whole_file:
1864 min_sdk_version = GetMinSdkVersionInt(
1865 input_name, codename_to_api_level_map)
1866 if min_sdk_version is not None:
1867 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1868
T.R. Fullhart37e10522013-03-18 10:31:26 -07001869 cmd.extend([key + OPTIONS.public_key_suffix,
1870 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001871 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001872
Tao Bao73dd4f42018-10-04 16:25:33 -07001873 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001874 if password is not None:
1875 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001876 stdoutdata, _ = proc.communicate(password)
1877 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001878 raise ExternalError(
1879 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001880 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001881
Doug Zongkereef39442009-04-02 12:14:19 -07001882
Doug Zongker37974732010-09-16 17:44:38 -07001883def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001884 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001885
Tao Bao9dd909e2017-11-14 11:27:32 -08001886 For non-AVB images, raise exception if the data is too big. Print a warning
1887 if the data is nearing the maximum size.
1888
1889 For AVB images, the actual image size should be identical to the limit.
1890
1891 Args:
1892 data: A string that contains all the data for the partition.
1893 target: The partition name. The ".img" suffix is optional.
1894 info_dict: The dict to be looked up for relevant info.
1895 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001896 if target.endswith(".img"):
1897 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001898 mount_point = "/" + target
1899
Ying Wangf8824af2014-06-03 14:07:27 -07001900 fs_type = None
1901 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001902 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001903 if mount_point == "/userdata":
1904 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001905 p = info_dict["fstab"][mount_point]
1906 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001907 device = p.device
1908 if "/" in device:
1909 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001910 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001911 if not fs_type or not limit:
1912 return
Doug Zongkereef39442009-04-02 12:14:19 -07001913
Andrew Boie0f9aec82012-02-14 09:32:52 -08001914 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001915 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1916 # path.
1917 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1918 if size != limit:
1919 raise ExternalError(
1920 "Mismatching image size for %s: expected %d actual %d" % (
1921 target, limit, size))
1922 else:
1923 pct = float(size) * 100.0 / limit
1924 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1925 if pct >= 99.0:
1926 raise ExternalError(msg)
1927 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001928 logger.warning("\n WARNING: %s\n", msg)
1929 else:
1930 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001931
1932
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001933def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001934 """Parses the APK certs info from a given target-files zip.
1935
1936 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1937 tuple with the following elements: (1) a dictionary that maps packages to
1938 certs (based on the "certificate" and "private_key" attributes in the file;
1939 (2) a string representing the extension of compressed APKs in the target files
1940 (e.g ".gz", ".bro").
1941
1942 Args:
1943 tf_zip: The input target_files ZipFile (already open).
1944
1945 Returns:
1946 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1947 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1948 no compressed APKs.
1949 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001950 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001951 compressed_extension = None
1952
Tao Bao0f990332017-09-08 19:02:54 -07001953 # META/apkcerts.txt contains the info for _all_ the packages known at build
1954 # time. Filter out the ones that are not installed.
1955 installed_files = set()
1956 for name in tf_zip.namelist():
1957 basename = os.path.basename(name)
1958 if basename:
1959 installed_files.add(basename)
1960
Tao Baoda30cfa2017-12-01 16:19:46 -08001961 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001962 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001963 if not line:
1964 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001965 m = re.match(
1966 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001967 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1968 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001969 line)
1970 if not m:
1971 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001972
Tao Bao818ddf52018-01-05 11:17:34 -08001973 matches = m.groupdict()
1974 cert = matches["CERT"]
1975 privkey = matches["PRIVKEY"]
1976 name = matches["NAME"]
1977 this_compressed_extension = matches["COMPRESSED"]
1978
1979 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1980 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1981 if cert in SPECIAL_CERT_STRINGS and not privkey:
1982 certmap[name] = cert
1983 elif (cert.endswith(OPTIONS.public_key_suffix) and
1984 privkey.endswith(OPTIONS.private_key_suffix) and
1985 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1986 certmap[name] = cert[:-public_key_suffix_len]
1987 else:
1988 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1989
1990 if not this_compressed_extension:
1991 continue
1992
1993 # Only count the installed files.
1994 filename = name + '.' + this_compressed_extension
1995 if filename not in installed_files:
1996 continue
1997
1998 # Make sure that all the values in the compression map have the same
1999 # extension. We don't support multiple compression methods in the same
2000 # system image.
2001 if compressed_extension:
2002 if this_compressed_extension != compressed_extension:
2003 raise ValueError(
2004 "Multiple compressed extensions: {} vs {}".format(
2005 compressed_extension, this_compressed_extension))
2006 else:
2007 compressed_extension = this_compressed_extension
2008
2009 return (certmap,
2010 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002011
2012
Doug Zongkereef39442009-04-02 12:14:19 -07002013COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002014Global options
2015
2016 -p (--path) <dir>
2017 Prepend <dir>/bin to the list of places to search for binaries run by this
2018 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002019
Doug Zongker05d3dea2009-06-22 11:32:31 -07002020 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002021 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002022
Tao Bao30df8b42018-04-23 15:32:53 -07002023 -x (--extra) <key=value>
2024 Add a key/value pair to the 'extras' dict, which device-specific extension
2025 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002026
Doug Zongkereef39442009-04-02 12:14:19 -07002027 -v (--verbose)
2028 Show command lines being executed.
2029
2030 -h (--help)
2031 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002032
2033 --logfile <file>
2034 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002035"""
2036
2037def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002038 print(docstring.rstrip("\n"))
2039 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002040
2041
2042def ParseOptions(argv,
2043 docstring,
2044 extra_opts="", extra_long_opts=(),
2045 extra_option_handler=None):
2046 """Parse the options in argv and return any arguments that aren't
2047 flags. docstring is the calling module's docstring, to be displayed
2048 for errors and -h. extra_opts and extra_long_opts are for flags
2049 defined by the caller, which are processed by passing them to
2050 extra_option_handler."""
2051
2052 try:
2053 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002054 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002055 ["help", "verbose", "path=", "signapk_path=",
2056 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002057 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002058 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2059 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002060 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2061 "aftl_key_path=", "aftl_manufacturer_key_path=",
2062 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002063 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002064 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002065 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002066 sys.exit(2)
2067
Doug Zongkereef39442009-04-02 12:14:19 -07002068 for o, a in opts:
2069 if o in ("-h", "--help"):
2070 Usage(docstring)
2071 sys.exit()
2072 elif o in ("-v", "--verbose"):
2073 OPTIONS.verbose = True
2074 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002075 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002076 elif o in ("--signapk_path",):
2077 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002078 elif o in ("--signapk_shared_library_path",):
2079 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002080 elif o in ("--extra_signapk_args",):
2081 OPTIONS.extra_signapk_args = shlex.split(a)
2082 elif o in ("--java_path",):
2083 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002084 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002085 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002086 elif o in ("--android_jar_path",):
2087 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002088 elif o in ("--public_key_suffix",):
2089 OPTIONS.public_key_suffix = a
2090 elif o in ("--private_key_suffix",):
2091 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002092 elif o in ("--boot_signer_path",):
2093 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002094 elif o in ("--boot_signer_args",):
2095 OPTIONS.boot_signer_args = shlex.split(a)
2096 elif o in ("--verity_signer_path",):
2097 OPTIONS.verity_signer_path = a
2098 elif o in ("--verity_signer_args",):
2099 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002100 elif o in ("--aftl_tool_path",):
2101 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002102 elif o in ("--aftl_server",):
2103 OPTIONS.aftl_server = a
2104 elif o in ("--aftl_key_path",):
2105 OPTIONS.aftl_key_path = a
2106 elif o in ("--aftl_manufacturer_key_path",):
2107 OPTIONS.aftl_manufacturer_key_path = a
2108 elif o in ("--aftl_signer_helper",):
2109 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002110 elif o in ("-s", "--device_specific"):
2111 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002112 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002113 key, value = a.split("=", 1)
2114 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002115 elif o in ("--logfile",):
2116 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002117 else:
2118 if extra_option_handler is None or not extra_option_handler(o, a):
2119 assert False, "unknown option \"%s\"" % (o,)
2120
Doug Zongker85448772014-09-09 14:59:20 -07002121 if OPTIONS.search_path:
2122 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2123 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002124
2125 return args
2126
2127
Tao Bao4c851b12016-09-19 13:54:38 -07002128def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002129 """Make a temp file and add it to the list of things to be deleted
2130 when Cleanup() is called. Return the filename."""
2131 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2132 os.close(fd)
2133 OPTIONS.tempfiles.append(fn)
2134 return fn
2135
2136
Tao Bao1c830bf2017-12-25 10:43:47 -08002137def MakeTempDir(prefix='tmp', suffix=''):
2138 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2139
2140 Returns:
2141 The absolute pathname of the new directory.
2142 """
2143 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2144 OPTIONS.tempfiles.append(dir_name)
2145 return dir_name
2146
2147
Doug Zongkereef39442009-04-02 12:14:19 -07002148def Cleanup():
2149 for i in OPTIONS.tempfiles:
2150 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002151 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002152 else:
2153 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002154 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002155
2156
2157class PasswordManager(object):
2158 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002159 self.editor = os.getenv("EDITOR")
2160 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002161
2162 def GetPasswords(self, items):
2163 """Get passwords corresponding to each string in 'items',
2164 returning a dict. (The dict may have keys in addition to the
2165 values in 'items'.)
2166
2167 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2168 user edit that file to add more needed passwords. If no editor is
2169 available, or $ANDROID_PW_FILE isn't define, prompts the user
2170 interactively in the ordinary way.
2171 """
2172
2173 current = self.ReadFile()
2174
2175 first = True
2176 while True:
2177 missing = []
2178 for i in items:
2179 if i not in current or not current[i]:
2180 missing.append(i)
2181 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002182 if not missing:
2183 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002184
2185 for i in missing:
2186 current[i] = ""
2187
2188 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002189 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002190 if sys.version_info[0] >= 3:
2191 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002192 answer = raw_input("try to edit again? [y]> ").strip()
2193 if answer and answer[0] not in 'yY':
2194 raise RuntimeError("key passwords unavailable")
2195 first = False
2196
2197 current = self.UpdateAndReadFile(current)
2198
Dan Albert8b72aef2015-03-23 19:13:21 -07002199 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002200 """Prompt the user to enter a value (password) for each key in
2201 'current' whose value is fales. Returns a new dict with all the
2202 values.
2203 """
2204 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002205 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002206 if v:
2207 result[k] = v
2208 else:
2209 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002210 result[k] = getpass.getpass(
2211 "Enter password for %s key> " % k).strip()
2212 if result[k]:
2213 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002214 return result
2215
2216 def UpdateAndReadFile(self, current):
2217 if not self.editor or not self.pwfile:
2218 return self.PromptResult(current)
2219
2220 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002221 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002222 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2223 f.write("# (Additional spaces are harmless.)\n\n")
2224
2225 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002226 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002227 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002228 f.write("[[[ %s ]]] %s\n" % (v, k))
2229 if not v and first_line is None:
2230 # position cursor on first line with no password.
2231 first_line = i + 4
2232 f.close()
2233
Tao Bao986ee862018-10-04 15:46:16 -07002234 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002235
2236 return self.ReadFile()
2237
2238 def ReadFile(self):
2239 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002240 if self.pwfile is None:
2241 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002242 try:
2243 f = open(self.pwfile, "r")
2244 for line in f:
2245 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 if not line or line[0] == '#':
2247 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002248 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2249 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002250 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002251 else:
2252 result[m.group(2)] = m.group(1)
2253 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002254 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002256 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002257 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002258
2259
Dan Albert8e0178d2015-01-27 15:53:15 -08002260def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2261 compress_type=None):
2262 import datetime
2263
2264 # http://b/18015246
2265 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2266 # for files larger than 2GiB. We can work around this by adjusting their
2267 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2268 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2269 # it isn't clear to me exactly what circumstances cause this).
2270 # `zipfile.write()` must be used directly to work around this.
2271 #
2272 # This mess can be avoided if we port to python3.
2273 saved_zip64_limit = zipfile.ZIP64_LIMIT
2274 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2275
2276 if compress_type is None:
2277 compress_type = zip_file.compression
2278 if arcname is None:
2279 arcname = filename
2280
2281 saved_stat = os.stat(filename)
2282
2283 try:
2284 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2285 # file to be zipped and reset it when we're done.
2286 os.chmod(filename, perms)
2287
2288 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002289 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2290 # intentional. zip stores datetimes in local time without a time zone
2291 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2292 # in the zip archive.
2293 local_epoch = datetime.datetime.fromtimestamp(0)
2294 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002295 os.utime(filename, (timestamp, timestamp))
2296
2297 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2298 finally:
2299 os.chmod(filename, saved_stat.st_mode)
2300 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2301 zipfile.ZIP64_LIMIT = saved_zip64_limit
2302
2303
Tao Bao58c1b962015-05-20 09:32:18 -07002304def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002305 compress_type=None):
2306 """Wrap zipfile.writestr() function to work around the zip64 limit.
2307
2308 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2309 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2310 when calling crc32(bytes).
2311
2312 But it still works fine to write a shorter string into a large zip file.
2313 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2314 when we know the string won't be too long.
2315 """
2316
2317 saved_zip64_limit = zipfile.ZIP64_LIMIT
2318 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2319
2320 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2321 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002322 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002323 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002324 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002325 else:
Tao Baof3282b42015-04-01 11:21:55 -07002326 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002327 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2328 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2329 # such a case (since
2330 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2331 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2332 # permission bits. We follow the logic in Python 3 to get consistent
2333 # behavior between using the two versions.
2334 if not zinfo.external_attr:
2335 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002336
2337 # If compress_type is given, it overrides the value in zinfo.
2338 if compress_type is not None:
2339 zinfo.compress_type = compress_type
2340
Tao Bao58c1b962015-05-20 09:32:18 -07002341 # If perms is given, it has a priority.
2342 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002343 # If perms doesn't set the file type, mark it as a regular file.
2344 if perms & 0o770000 == 0:
2345 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002346 zinfo.external_attr = perms << 16
2347
Tao Baof3282b42015-04-01 11:21:55 -07002348 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002349 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2350
Dan Albert8b72aef2015-03-23 19:13:21 -07002351 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002352 zipfile.ZIP64_LIMIT = saved_zip64_limit
2353
2354
Tao Bao89d7ab22017-12-14 17:05:33 -08002355def ZipDelete(zip_filename, entries):
2356 """Deletes entries from a ZIP file.
2357
2358 Since deleting entries from a ZIP file is not supported, it shells out to
2359 'zip -d'.
2360
2361 Args:
2362 zip_filename: The name of the ZIP file.
2363 entries: The name of the entry, or the list of names to be deleted.
2364
2365 Raises:
2366 AssertionError: In case of non-zero return from 'zip'.
2367 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002368 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002369 entries = [entries]
2370 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002371 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002372
2373
Tao Baof3282b42015-04-01 11:21:55 -07002374def ZipClose(zip_file):
2375 # http://b/18015246
2376 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2377 # central directory.
2378 saved_zip64_limit = zipfile.ZIP64_LIMIT
2379 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2380
2381 zip_file.close()
2382
2383 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002384
2385
2386class DeviceSpecificParams(object):
2387 module = None
2388 def __init__(self, **kwargs):
2389 """Keyword arguments to the constructor become attributes of this
2390 object, which is passed to all functions in the device-specific
2391 module."""
Tao Bao38884282019-07-10 22:20:56 -07002392 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002393 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002394 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002395
2396 if self.module is None:
2397 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002398 if not path:
2399 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002400 try:
2401 if os.path.isdir(path):
2402 info = imp.find_module("releasetools", [path])
2403 else:
2404 d, f = os.path.split(path)
2405 b, x = os.path.splitext(f)
2406 if x == ".py":
2407 f = b
2408 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002409 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002410 self.module = imp.load_module("device_specific", *info)
2411 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002412 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002413
2414 def _DoCall(self, function_name, *args, **kwargs):
2415 """Call the named function in the device-specific module, passing
2416 the given args and kwargs. The first argument to the call will be
2417 the DeviceSpecific object itself. If there is no module, or the
2418 module does not define the function, return the value of the
2419 'default' kwarg (which itself defaults to None)."""
2420 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002421 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002422 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2423
2424 def FullOTA_Assertions(self):
2425 """Called after emitting the block of assertions at the top of a
2426 full OTA package. Implementations can add whatever additional
2427 assertions they like."""
2428 return self._DoCall("FullOTA_Assertions")
2429
Doug Zongkere5ff5902012-01-17 10:55:37 -08002430 def FullOTA_InstallBegin(self):
2431 """Called at the start of full OTA installation."""
2432 return self._DoCall("FullOTA_InstallBegin")
2433
Yifan Hong10c530d2018-12-27 17:34:18 -08002434 def FullOTA_GetBlockDifferences(self):
2435 """Called during full OTA installation and verification.
2436 Implementation should return a list of BlockDifference objects describing
2437 the update on each additional partitions.
2438 """
2439 return self._DoCall("FullOTA_GetBlockDifferences")
2440
Doug Zongker05d3dea2009-06-22 11:32:31 -07002441 def FullOTA_InstallEnd(self):
2442 """Called at the end of full OTA installation; typically this is
2443 used to install the image for the device's baseband processor."""
2444 return self._DoCall("FullOTA_InstallEnd")
2445
2446 def IncrementalOTA_Assertions(self):
2447 """Called after emitting the block of assertions at the top of an
2448 incremental OTA package. Implementations can add whatever
2449 additional assertions they like."""
2450 return self._DoCall("IncrementalOTA_Assertions")
2451
Doug Zongkere5ff5902012-01-17 10:55:37 -08002452 def IncrementalOTA_VerifyBegin(self):
2453 """Called at the start of the verification phase of incremental
2454 OTA installation; additional checks can be placed here to abort
2455 the script before any changes are made."""
2456 return self._DoCall("IncrementalOTA_VerifyBegin")
2457
Doug Zongker05d3dea2009-06-22 11:32:31 -07002458 def IncrementalOTA_VerifyEnd(self):
2459 """Called at the end of the verification phase of incremental OTA
2460 installation; additional checks can be placed here to abort the
2461 script before any changes are made."""
2462 return self._DoCall("IncrementalOTA_VerifyEnd")
2463
Doug Zongkere5ff5902012-01-17 10:55:37 -08002464 def IncrementalOTA_InstallBegin(self):
2465 """Called at the start of incremental OTA installation (after
2466 verification is complete)."""
2467 return self._DoCall("IncrementalOTA_InstallBegin")
2468
Yifan Hong10c530d2018-12-27 17:34:18 -08002469 def IncrementalOTA_GetBlockDifferences(self):
2470 """Called during incremental OTA installation and verification.
2471 Implementation should return a list of BlockDifference objects describing
2472 the update on each additional partitions.
2473 """
2474 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2475
Doug Zongker05d3dea2009-06-22 11:32:31 -07002476 def IncrementalOTA_InstallEnd(self):
2477 """Called at the end of incremental OTA installation; typically
2478 this is used to install the image for the device's baseband
2479 processor."""
2480 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002481
Tao Bao9bc6bb22015-11-09 16:58:28 -08002482 def VerifyOTA_Assertions(self):
2483 return self._DoCall("VerifyOTA_Assertions")
2484
Tao Bao76def242017-11-21 09:25:31 -08002485
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002486class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002487 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002488 self.name = name
2489 self.data = data
2490 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002491 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002492 self.sha1 = sha1(data).hexdigest()
2493
2494 @classmethod
2495 def FromLocalFile(cls, name, diskname):
2496 f = open(diskname, "rb")
2497 data = f.read()
2498 f.close()
2499 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002500
2501 def WriteToTemp(self):
2502 t = tempfile.NamedTemporaryFile()
2503 t.write(self.data)
2504 t.flush()
2505 return t
2506
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002507 def WriteToDir(self, d):
2508 with open(os.path.join(d, self.name), "wb") as fp:
2509 fp.write(self.data)
2510
Geremy Condra36bd3652014-02-06 19:45:10 -08002511 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002512 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002513
Tao Bao76def242017-11-21 09:25:31 -08002514
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002515DIFF_PROGRAM_BY_EXT = {
2516 ".gz" : "imgdiff",
2517 ".zip" : ["imgdiff", "-z"],
2518 ".jar" : ["imgdiff", "-z"],
2519 ".apk" : ["imgdiff", "-z"],
2520 ".img" : "imgdiff",
2521 }
2522
Tao Bao76def242017-11-21 09:25:31 -08002523
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002524class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002525 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002526 self.tf = tf
2527 self.sf = sf
2528 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002529 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002530
2531 def ComputePatch(self):
2532 """Compute the patch (as a string of data) needed to turn sf into
2533 tf. Returns the same tuple as GetPatch()."""
2534
2535 tf = self.tf
2536 sf = self.sf
2537
Doug Zongker24cd2802012-08-14 16:36:15 -07002538 if self.diff_program:
2539 diff_program = self.diff_program
2540 else:
2541 ext = os.path.splitext(tf.name)[1]
2542 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002543
2544 ttemp = tf.WriteToTemp()
2545 stemp = sf.WriteToTemp()
2546
2547 ext = os.path.splitext(tf.name)[1]
2548
2549 try:
2550 ptemp = tempfile.NamedTemporaryFile()
2551 if isinstance(diff_program, list):
2552 cmd = copy.copy(diff_program)
2553 else:
2554 cmd = [diff_program]
2555 cmd.append(stemp.name)
2556 cmd.append(ttemp.name)
2557 cmd.append(ptemp.name)
2558 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002559 err = []
2560 def run():
2561 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002562 if e:
2563 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002564 th = threading.Thread(target=run)
2565 th.start()
2566 th.join(timeout=300) # 5 mins
2567 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002568 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002569 p.terminate()
2570 th.join(5)
2571 if th.is_alive():
2572 p.kill()
2573 th.join()
2574
Tianjie Xua2a9f992018-01-05 15:15:54 -08002575 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002576 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002577 self.patch = None
2578 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002579 diff = ptemp.read()
2580 finally:
2581 ptemp.close()
2582 stemp.close()
2583 ttemp.close()
2584
2585 self.patch = diff
2586 return self.tf, self.sf, self.patch
2587
2588
2589 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002590 """Returns a tuple of (target_file, source_file, patch_data).
2591
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002592 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002593 computing the patch failed.
2594 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002595 return self.tf, self.sf, self.patch
2596
2597
2598def ComputeDifferences(diffs):
2599 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002600 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002601
2602 # Do the largest files first, to try and reduce the long-pole effect.
2603 by_size = [(i.tf.size, i) for i in diffs]
2604 by_size.sort(reverse=True)
2605 by_size = [i[1] for i in by_size]
2606
2607 lock = threading.Lock()
2608 diff_iter = iter(by_size) # accessed under lock
2609
2610 def worker():
2611 try:
2612 lock.acquire()
2613 for d in diff_iter:
2614 lock.release()
2615 start = time.time()
2616 d.ComputePatch()
2617 dur = time.time() - start
2618 lock.acquire()
2619
2620 tf, sf, patch = d.GetPatch()
2621 if sf.name == tf.name:
2622 name = tf.name
2623 else:
2624 name = "%s (%s)" % (tf.name, sf.name)
2625 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002626 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002627 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002628 logger.info(
2629 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2630 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002631 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002632 except Exception:
2633 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002634 raise
2635
2636 # start worker threads; wait for them all to finish.
2637 threads = [threading.Thread(target=worker)
2638 for i in range(OPTIONS.worker_threads)]
2639 for th in threads:
2640 th.start()
2641 while threads:
2642 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002643
2644
Dan Albert8b72aef2015-03-23 19:13:21 -07002645class BlockDifference(object):
2646 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002647 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002648 self.tgt = tgt
2649 self.src = src
2650 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002651 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002652 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002653
Tao Baodd2a5892015-03-12 12:32:37 -07002654 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002655 version = max(
2656 int(i) for i in
2657 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002658 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002659 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002660
Tianjie Xu41976c72019-07-03 13:57:01 -07002661 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2662 version=self.version,
2663 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002664 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002665 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002666 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002667 self.touched_src_ranges = b.touched_src_ranges
2668 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002669
Yifan Hong10c530d2018-12-27 17:34:18 -08002670 # On devices with dynamic partitions, for new partitions,
2671 # src is None but OPTIONS.source_info_dict is not.
2672 if OPTIONS.source_info_dict is None:
2673 is_dynamic_build = OPTIONS.info_dict.get(
2674 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002675 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002676 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002677 is_dynamic_build = OPTIONS.source_info_dict.get(
2678 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002679 is_dynamic_source = partition in shlex.split(
2680 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002681
Yifan Hongbb2658d2019-01-25 12:30:58 -08002682 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002683 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2684
Yifan Hongbb2658d2019-01-25 12:30:58 -08002685 # For dynamic partitions builds, check partition list in both source
2686 # and target build because new partitions may be added, and existing
2687 # partitions may be removed.
2688 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2689
Yifan Hong10c530d2018-12-27 17:34:18 -08002690 if is_dynamic:
2691 self.device = 'map_partition("%s")' % partition
2692 else:
2693 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002694 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2695 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002696 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002697 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2698 OPTIONS.source_info_dict)
2699 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002700
Tao Baod8d14be2016-02-04 14:26:02 -08002701 @property
2702 def required_cache(self):
2703 return self._required_cache
2704
Tao Bao76def242017-11-21 09:25:31 -08002705 def WriteScript(self, script, output_zip, progress=None,
2706 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002707 if not self.src:
2708 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002709 script.Print("Patching %s image unconditionally..." % (self.partition,))
2710 else:
2711 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002712
Dan Albert8b72aef2015-03-23 19:13:21 -07002713 if progress:
2714 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002715 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002716
2717 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002718 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002719
Tao Bao9bc6bb22015-11-09 16:58:28 -08002720 def WriteStrictVerifyScript(self, script):
2721 """Verify all the blocks in the care_map, including clobbered blocks.
2722
2723 This differs from the WriteVerifyScript() function: a) it prints different
2724 error messages; b) it doesn't allow half-way updated images to pass the
2725 verification."""
2726
2727 partition = self.partition
2728 script.Print("Verifying %s..." % (partition,))
2729 ranges = self.tgt.care_map
2730 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002731 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002732 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2733 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002734 self.device, ranges_str,
2735 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002737 script.AppendExtra("")
2738
Tao Baod522bdc2016-04-12 15:53:16 -07002739 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002740 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002741
2742 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002743 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002744 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002745
2746 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002747 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002748 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002749 ranges = self.touched_src_ranges
2750 expected_sha1 = self.touched_src_sha1
2751 else:
2752 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2753 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002754
2755 # No blocks to be checked, skipping.
2756 if not ranges:
2757 return
2758
Tao Bao5ece99d2015-05-12 11:42:31 -07002759 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002760 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002761 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002762 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2763 '"%s.patch.dat")) then' % (
2764 self.device, ranges_str, expected_sha1,
2765 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002766 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002767 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002768
Tianjie Xufc3422a2015-12-15 11:53:59 -08002769 if self.version >= 4:
2770
2771 # Bug: 21124327
2772 # When generating incrementals for the system and vendor partitions in
2773 # version 4 or newer, explicitly check the first block (which contains
2774 # the superblock) of the partition to see if it's what we expect. If
2775 # this check fails, give an explicit log message about the partition
2776 # having been remounted R/W (the most likely explanation).
2777 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002778 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002779
2780 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002781 if partition == "system":
2782 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2783 else:
2784 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002785 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002786 'ifelse (block_image_recover({device}, "{ranges}") && '
2787 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002788 'package_extract_file("{partition}.transfer.list"), '
2789 '"{partition}.new.dat", "{partition}.patch.dat"), '
2790 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002791 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002792 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002793 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002794
Tao Baodd2a5892015-03-12 12:32:37 -07002795 # Abort the OTA update. Note that the incremental OTA cannot be applied
2796 # even if it may match the checksum of the target partition.
2797 # a) If version < 3, operations like move and erase will make changes
2798 # unconditionally and damage the partition.
2799 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002800 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002801 if partition == "system":
2802 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2803 else:
2804 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2805 script.AppendExtra((
2806 'abort("E%d: %s partition has unexpected contents");\n'
2807 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002808
Yifan Hong10c530d2018-12-27 17:34:18 -08002809 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002810 partition = self.partition
2811 script.Print('Verifying the updated %s image...' % (partition,))
2812 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2813 ranges = self.tgt.care_map
2814 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002815 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002816 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002817 self.device, ranges_str,
2818 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002819
2820 # Bug: 20881595
2821 # Verify that extended blocks are really zeroed out.
2822 if self.tgt.extended:
2823 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002824 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002825 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002826 self.device, ranges_str,
2827 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002828 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002829 if partition == "system":
2830 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2831 else:
2832 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002833 script.AppendExtra(
2834 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002835 ' abort("E%d: %s partition has unexpected non-zero contents after '
2836 'OTA update");\n'
2837 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002838 else:
2839 script.Print('Verified the updated %s image.' % (partition,))
2840
Tianjie Xu209db462016-05-24 17:34:52 -07002841 if partition == "system":
2842 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2843 else:
2844 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2845
Tao Bao5fcaaef2015-06-01 13:40:49 -07002846 script.AppendExtra(
2847 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002848 ' abort("E%d: %s partition has unexpected contents after OTA '
2849 'update");\n'
2850 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002851
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002852 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002853 ZipWrite(output_zip,
2854 '{}.transfer.list'.format(self.path),
2855 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002856
Tao Bao76def242017-11-21 09:25:31 -08002857 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2858 # its size. Quailty 9 almost triples the compression time but doesn't
2859 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002860 # zip | brotli(quality 6) | brotli(quality 9)
2861 # compressed_size: 942M | 869M (~8% reduced) | 854M
2862 # compression_time: 75s | 265s | 719s
2863 # decompression_time: 15s | 25s | 25s
2864
2865 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002866 brotli_cmd = ['brotli', '--quality=6',
2867 '--output={}.new.dat.br'.format(self.path),
2868 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002869 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002870 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002871
2872 new_data_name = '{}.new.dat.br'.format(self.partition)
2873 ZipWrite(output_zip,
2874 '{}.new.dat.br'.format(self.path),
2875 new_data_name,
2876 compress_type=zipfile.ZIP_STORED)
2877 else:
2878 new_data_name = '{}.new.dat'.format(self.partition)
2879 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2880
Dan Albert8e0178d2015-01-27 15:53:15 -08002881 ZipWrite(output_zip,
2882 '{}.patch.dat'.format(self.path),
2883 '{}.patch.dat'.format(self.partition),
2884 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002885
Tianjie Xu209db462016-05-24 17:34:52 -07002886 if self.partition == "system":
2887 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2888 else:
2889 code = ErrorCode.VENDOR_UPDATE_FAILURE
2890
Yifan Hong10c530d2018-12-27 17:34:18 -08002891 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002892 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002893 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002894 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002895 device=self.device, partition=self.partition,
2896 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002897 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002898
Dan Albert8b72aef2015-03-23 19:13:21 -07002899 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002900 data = source.ReadRangeSet(ranges)
2901 ctx = sha1()
2902
2903 for p in data:
2904 ctx.update(p)
2905
2906 return ctx.hexdigest()
2907
Tao Baoe9b61912015-07-09 17:37:49 -07002908 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2909 """Return the hash value for all zero blocks."""
2910 zero_block = '\x00' * 4096
2911 ctx = sha1()
2912 for _ in range(num_blocks):
2913 ctx.update(zero_block)
2914
2915 return ctx.hexdigest()
2916
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002917
Tianjie Xu41976c72019-07-03 13:57:01 -07002918# Expose these two classes to support vendor-specific scripts
2919DataImage = images.DataImage
2920EmptyImage = images.EmptyImage
2921
Tao Bao76def242017-11-21 09:25:31 -08002922
Doug Zongker96a57e72010-09-26 14:57:41 -07002923# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002924PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002925 "ext4": "EMMC",
2926 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002927 "f2fs": "EMMC",
2928 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002929}
Doug Zongker96a57e72010-09-26 14:57:41 -07002930
Yifan Hongbdb32012020-05-07 12:38:53 -07002931def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2932 """
2933 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2934 backwards compatibility. It aborts if the fstab entry has slotselect option
2935 (unless check_no_slot is explicitly set to False).
2936 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002937 fstab = info["fstab"]
2938 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002939 if check_no_slot:
2940 assert not fstab[mount_point].slotselect, \
2941 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002942 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2943 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002944 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002945 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002946
2947
Yifan Hongbdb32012020-05-07 12:38:53 -07002948def GetTypeAndDeviceExpr(mount_point, info):
2949 """
2950 Return the filesystem of the partition, and an edify expression that evaluates
2951 to the device at runtime.
2952 """
2953 fstab = info["fstab"]
2954 if fstab:
2955 p = fstab[mount_point]
2956 device_expr = '"%s"' % fstab[mount_point].device
2957 if p.slotselect:
2958 device_expr = 'add_slot_suffix(%s)' % device_expr
2959 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2960 else:
2961 raise KeyError
2962
2963
2964def GetEntryForDevice(fstab, device):
2965 """
2966 Returns:
2967 The first entry in fstab whose device is the given value.
2968 """
2969 if not fstab:
2970 return None
2971 for mount_point in fstab:
2972 if fstab[mount_point].device == device:
2973 return fstab[mount_point]
2974 return None
2975
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002976def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002977 """Parses and converts a PEM-encoded certificate into DER-encoded.
2978
2979 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2980
2981 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002982 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002983 """
2984 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002985 save = False
2986 for line in data.split("\n"):
2987 if "--END CERTIFICATE--" in line:
2988 break
2989 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002990 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002991 if "--BEGIN CERTIFICATE--" in line:
2992 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002993 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002994 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002995
Tao Bao04e1f012018-02-04 12:13:35 -08002996
2997def ExtractPublicKey(cert):
2998 """Extracts the public key (PEM-encoded) from the given certificate file.
2999
3000 Args:
3001 cert: The certificate filename.
3002
3003 Returns:
3004 The public key string.
3005
3006 Raises:
3007 AssertionError: On non-zero return from 'openssl'.
3008 """
3009 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3010 # While openssl 1.1 writes the key into the given filename followed by '-out',
3011 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3012 # stdout instead.
3013 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3014 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3015 pubkey, stderrdata = proc.communicate()
3016 assert proc.returncode == 0, \
3017 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3018 return pubkey
3019
3020
Tao Bao1ac886e2019-06-26 11:58:22 -07003021def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003022 """Extracts the AVB public key from the given public or private key.
3023
3024 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003025 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003026 key: The input key file, which should be PEM-encoded public or private key.
3027
3028 Returns:
3029 The path to the extracted AVB public key file.
3030 """
3031 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3032 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003033 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003034 return output
3035
3036
Doug Zongker412c02f2014-02-13 10:58:24 -08003037def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3038 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003039 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003040
Tao Bao6d5d6232018-03-09 17:04:42 -08003041 Most of the space in the boot and recovery images is just the kernel, which is
3042 identical for the two, so the resulting patch should be efficient. Add it to
3043 the output zip, along with a shell script that is run from init.rc on first
3044 boot to actually do the patching and install the new recovery image.
3045
3046 Args:
3047 input_dir: The top-level input directory of the target-files.zip.
3048 output_sink: The callback function that writes the result.
3049 recovery_img: File object for the recovery image.
3050 boot_img: File objects for the boot image.
3051 info_dict: A dict returned by common.LoadInfoDict() on the input
3052 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003053 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003054 if info_dict is None:
3055 info_dict = OPTIONS.info_dict
3056
Tao Bao6d5d6232018-03-09 17:04:42 -08003057 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003058 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3059
3060 if board_uses_vendorimage:
3061 # In this case, the output sink is rooted at VENDOR
3062 recovery_img_path = "etc/recovery.img"
3063 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3064 sh_dir = "bin"
3065 else:
3066 # In this case the output sink is rooted at SYSTEM
3067 recovery_img_path = "vendor/etc/recovery.img"
3068 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3069 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003070
Tao Baof2cffbd2015-07-22 12:33:18 -07003071 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003072 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003073
3074 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003075 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003076 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003077 # With system-root-image, boot and recovery images will have mismatching
3078 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3079 # to handle such a case.
3080 if system_root_image:
3081 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003082 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003083 assert not os.path.exists(path)
3084 else:
3085 diff_program = ["imgdiff"]
3086 if os.path.exists(path):
3087 diff_program.append("-b")
3088 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003089 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003090 else:
3091 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003092
3093 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3094 _, _, patch = d.ComputePatch()
3095 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003096
Dan Albertebb19aa2015-03-27 19:11:53 -07003097 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003098 # The following GetTypeAndDevice()s need to use the path in the target
3099 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003100 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3101 check_no_slot=False)
3102 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3103 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003104 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003105 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003106
Tao Baof2cffbd2015-07-22 12:33:18 -07003107 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003108
3109 # Note that we use /vendor to refer to the recovery resources. This will
3110 # work for a separate vendor partition mounted at /vendor or a
3111 # /system/vendor subdirectory on the system partition, for which init will
3112 # create a symlink from /vendor to /system/vendor.
3113
3114 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003115if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3116 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003117 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003118 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3119 log -t recovery "Installing new recovery image: succeeded" || \\
3120 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003121else
3122 log -t recovery "Recovery image already installed"
3123fi
3124""" % {'type': recovery_type,
3125 'device': recovery_device,
3126 'sha1': recovery_img.sha1,
3127 'size': recovery_img.size}
3128 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003129 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003130if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3131 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003132 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003133 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3134 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3135 log -t recovery "Installing new recovery image: succeeded" || \\
3136 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003137else
3138 log -t recovery "Recovery image already installed"
3139fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003140""" % {'boot_size': boot_img.size,
3141 'boot_sha1': boot_img.sha1,
3142 'recovery_size': recovery_img.size,
3143 'recovery_sha1': recovery_img.sha1,
3144 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003145 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3146 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003147 'recovery_device': recovery_device,
3148 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003149
Bill Peckhame868aec2019-09-17 17:06:47 -07003150 # The install script location moved from /system/etc to /system/bin in the L
3151 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3152 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003153
Tao Bao32fcdab2018-10-12 10:30:39 -07003154 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003155
Tao Baoda30cfa2017-12-01 16:19:46 -08003156 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003157
3158
3159class DynamicPartitionUpdate(object):
3160 def __init__(self, src_group=None, tgt_group=None, progress=None,
3161 block_difference=None):
3162 self.src_group = src_group
3163 self.tgt_group = tgt_group
3164 self.progress = progress
3165 self.block_difference = block_difference
3166
3167 @property
3168 def src_size(self):
3169 if not self.block_difference:
3170 return 0
3171 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3172
3173 @property
3174 def tgt_size(self):
3175 if not self.block_difference:
3176 return 0
3177 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3178
3179 @staticmethod
3180 def _GetSparseImageSize(img):
3181 if not img:
3182 return 0
3183 return img.blocksize * img.total_blocks
3184
3185
3186class DynamicGroupUpdate(object):
3187 def __init__(self, src_size=None, tgt_size=None):
3188 # None: group does not exist. 0: no size limits.
3189 self.src_size = src_size
3190 self.tgt_size = tgt_size
3191
3192
3193class DynamicPartitionsDifference(object):
3194 def __init__(self, info_dict, block_diffs, progress_dict=None,
3195 source_info_dict=None):
3196 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003197 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003198
3199 self._remove_all_before_apply = False
3200 if source_info_dict is None:
3201 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003202 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003203
Tao Baof1113e92019-06-18 12:10:14 -07003204 block_diff_dict = collections.OrderedDict(
3205 [(e.partition, e) for e in block_diffs])
3206
Yifan Hong10c530d2018-12-27 17:34:18 -08003207 assert len(block_diff_dict) == len(block_diffs), \
3208 "Duplicated BlockDifference object for {}".format(
3209 [partition for partition, count in
3210 collections.Counter(e.partition for e in block_diffs).items()
3211 if count > 1])
3212
Yifan Hong79997e52019-01-23 16:56:19 -08003213 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003214
3215 for p, block_diff in block_diff_dict.items():
3216 self._partition_updates[p] = DynamicPartitionUpdate()
3217 self._partition_updates[p].block_difference = block_diff
3218
3219 for p, progress in progress_dict.items():
3220 if p in self._partition_updates:
3221 self._partition_updates[p].progress = progress
3222
3223 tgt_groups = shlex.split(info_dict.get(
3224 "super_partition_groups", "").strip())
3225 src_groups = shlex.split(source_info_dict.get(
3226 "super_partition_groups", "").strip())
3227
3228 for g in tgt_groups:
3229 for p in shlex.split(info_dict.get(
3230 "super_%s_partition_list" % g, "").strip()):
3231 assert p in self._partition_updates, \
3232 "{} is in target super_{}_partition_list but no BlockDifference " \
3233 "object is provided.".format(p, g)
3234 self._partition_updates[p].tgt_group = g
3235
3236 for g in src_groups:
3237 for p in shlex.split(source_info_dict.get(
3238 "super_%s_partition_list" % g, "").strip()):
3239 assert p in self._partition_updates, \
3240 "{} is in source super_{}_partition_list but no BlockDifference " \
3241 "object is provided.".format(p, g)
3242 self._partition_updates[p].src_group = g
3243
Yifan Hong45433e42019-01-18 13:55:25 -08003244 target_dynamic_partitions = set(shlex.split(info_dict.get(
3245 "dynamic_partition_list", "").strip()))
3246 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3247 if u.tgt_size)
3248 assert block_diffs_with_target == target_dynamic_partitions, \
3249 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3250 list(target_dynamic_partitions), list(block_diffs_with_target))
3251
3252 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3253 "dynamic_partition_list", "").strip()))
3254 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3255 if u.src_size)
3256 assert block_diffs_with_source == source_dynamic_partitions, \
3257 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3258 list(source_dynamic_partitions), list(block_diffs_with_source))
3259
Yifan Hong10c530d2018-12-27 17:34:18 -08003260 if self._partition_updates:
3261 logger.info("Updating dynamic partitions %s",
3262 self._partition_updates.keys())
3263
Yifan Hong79997e52019-01-23 16:56:19 -08003264 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003265
3266 for g in tgt_groups:
3267 self._group_updates[g] = DynamicGroupUpdate()
3268 self._group_updates[g].tgt_size = int(info_dict.get(
3269 "super_%s_group_size" % g, "0").strip())
3270
3271 for g in src_groups:
3272 if g not in self._group_updates:
3273 self._group_updates[g] = DynamicGroupUpdate()
3274 self._group_updates[g].src_size = int(source_info_dict.get(
3275 "super_%s_group_size" % g, "0").strip())
3276
3277 self._Compute()
3278
3279 def WriteScript(self, script, output_zip, write_verify_script=False):
3280 script.Comment('--- Start patching dynamic partitions ---')
3281 for p, u in self._partition_updates.items():
3282 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3283 script.Comment('Patch partition %s' % p)
3284 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3285 write_verify_script=False)
3286
3287 op_list_path = MakeTempFile()
3288 with open(op_list_path, 'w') as f:
3289 for line in self._op_list:
3290 f.write('{}\n'.format(line))
3291
3292 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3293
3294 script.Comment('Update dynamic partition metadata')
3295 script.AppendExtra('assert(update_dynamic_partitions('
3296 'package_extract_file("dynamic_partitions_op_list")));')
3297
3298 if write_verify_script:
3299 for p, u in self._partition_updates.items():
3300 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3301 u.block_difference.WritePostInstallVerifyScript(script)
3302 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3303
3304 for p, u in self._partition_updates.items():
3305 if u.tgt_size and u.src_size <= u.tgt_size:
3306 script.Comment('Patch partition %s' % p)
3307 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3308 write_verify_script=write_verify_script)
3309 if write_verify_script:
3310 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3311
3312 script.Comment('--- End patching dynamic partitions ---')
3313
3314 def _Compute(self):
3315 self._op_list = list()
3316
3317 def append(line):
3318 self._op_list.append(line)
3319
3320 def comment(line):
3321 self._op_list.append("# %s" % line)
3322
3323 if self._remove_all_before_apply:
3324 comment('Remove all existing dynamic partitions and groups before '
3325 'applying full OTA')
3326 append('remove_all_groups')
3327
3328 for p, u in self._partition_updates.items():
3329 if u.src_group and not u.tgt_group:
3330 append('remove %s' % p)
3331
3332 for p, u in self._partition_updates.items():
3333 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3334 comment('Move partition %s from %s to default' % (p, u.src_group))
3335 append('move %s default' % p)
3336
3337 for p, u in self._partition_updates.items():
3338 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3339 comment('Shrink partition %s from %d to %d' %
3340 (p, u.src_size, u.tgt_size))
3341 append('resize %s %s' % (p, u.tgt_size))
3342
3343 for g, u in self._group_updates.items():
3344 if u.src_size is not None and u.tgt_size is None:
3345 append('remove_group %s' % g)
3346 if (u.src_size is not None and u.tgt_size is not None and
3347 u.src_size > u.tgt_size):
3348 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3349 append('resize_group %s %d' % (g, u.tgt_size))
3350
3351 for g, u in self._group_updates.items():
3352 if u.src_size is None and u.tgt_size is not None:
3353 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3354 append('add_group %s %d' % (g, u.tgt_size))
3355 if (u.src_size is not None and u.tgt_size is not None and
3356 u.src_size < u.tgt_size):
3357 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3358 append('resize_group %s %d' % (g, u.tgt_size))
3359
3360 for p, u in self._partition_updates.items():
3361 if u.tgt_group and not u.src_group:
3362 comment('Add partition %s to group %s' % (p, u.tgt_group))
3363 append('add %s %s' % (p, u.tgt_group))
3364
3365 for p, u in self._partition_updates.items():
3366 if u.tgt_size and u.src_size < u.tgt_size:
3367 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3368 append('resize %s %d' % (p, u.tgt_size))
3369
3370 for p, u in self._partition_updates.items():
3371 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3372 comment('Move partition %s from default to %s' %
3373 (p, u.tgt_group))
3374 append('move %s %s' % (p, u.tgt_group))