blob: 96f93a82c7cab54088f1b3343c04e46cdcdb45d1 [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
1135 values. Only valid partition names are accepted, as listed in
1136 common.AVB_PARTITIONS.
1137 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1138 needed_partitions: Partitions whose descriptors should be included into the
1139 generated VBMeta image.
1140
1141 Raises:
1142 AssertionError: On invalid input args.
1143 """
1144 avbtool = OPTIONS.info_dict["avb_avbtool"]
1145 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1146 AppendAVBSigningArgs(cmd, name)
1147
1148 for partition, path in partitions.items():
1149 if partition not in needed_partitions:
1150 continue
1151 assert (partition in AVB_PARTITIONS or
1152 partition in AVB_VBMETA_PARTITIONS), \
1153 'Unknown partition: {}'.format(partition)
1154 assert os.path.exists(path), \
1155 'Failed to find {} for {}'.format(path, partition)
1156 cmd.extend(GetAvbPartitionArg(partition, path))
1157
1158 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1159 if args and args.strip():
1160 split_args = shlex.split(args)
1161 for index, arg in enumerate(split_args[:-1]):
1162 # Sanity check that the image file exists. Some images might be defined
1163 # as a path relative to source tree, which may not be available at the
1164 # same location when running this script (we have the input target_files
1165 # zip only). For such cases, we additionally scan other locations (e.g.
1166 # IMAGES/, RADIO/, etc) before bailing out.
1167 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001168 chained_image = split_args[index + 1]
1169 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001170 continue
1171 found = False
1172 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1173 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001174 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001175 if os.path.exists(alt_path):
1176 split_args[index + 1] = alt_path
1177 found = True
1178 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001179 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001180 cmd.extend(split_args)
1181
1182 RunAndCheckOutput(cmd)
1183
Tianjie Xueaed60c2020-03-12 00:33:28 -07001184 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001185 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001186 AddAftlInclusionProof(image_path)
1187
Daniel Norman276f0622019-07-26 14:13:51 -07001188
Steve Mucklee1b10862019-07-10 10:49:37 -07001189def _MakeRamdisk(sourcedir, fs_config_file=None):
1190 ramdisk_img = tempfile.NamedTemporaryFile()
1191
1192 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1193 cmd = ["mkbootfs", "-f", fs_config_file,
1194 os.path.join(sourcedir, "RAMDISK")]
1195 else:
1196 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1197 p1 = Run(cmd, stdout=subprocess.PIPE)
1198 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1199
1200 p2.wait()
1201 p1.wait()
1202 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1203 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1204
1205 return ramdisk_img
1206
1207
Steve Muckle9793cf62020-04-08 18:27:00 -07001208def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001209 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001210 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001211
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001212 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001213 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1214 we are building a two-step special image (i.e. building a recovery image to
1215 be loaded into /boot in two-step OTAs).
1216
1217 Return the image data, or None if sourcedir does not appear to contains files
1218 for building the requested image.
1219 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001220
Steve Muckle9793cf62020-04-08 18:27:00 -07001221 # "boot" or "recovery", without extension.
1222 partition_name = os.path.basename(sourcedir).lower()
1223
1224 if partition_name == "recovery":
1225 kernel = "kernel"
1226 else:
1227 kernel = image_name.replace("boot", "kernel")
1228 kernel = kernel.replace(".img","")
1229 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001230 return None
1231
1232 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001233 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001234
Doug Zongkerd5131602012-08-02 14:46:42 -07001235 if info_dict is None:
1236 info_dict = OPTIONS.info_dict
1237
Doug Zongkereef39442009-04-02 12:14:19 -07001238 img = tempfile.NamedTemporaryFile()
1239
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001240 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001241 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001242
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001243 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1244 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1245
Steve Muckle9793cf62020-04-08 18:27:00 -07001246 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001247
Benoit Fradina45a8682014-07-14 21:00:43 +02001248 fn = os.path.join(sourcedir, "second")
1249 if os.access(fn, os.F_OK):
1250 cmd.append("--second")
1251 cmd.append(fn)
1252
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001253 fn = os.path.join(sourcedir, "dtb")
1254 if os.access(fn, os.F_OK):
1255 cmd.append("--dtb")
1256 cmd.append(fn)
1257
Doug Zongker171f1cd2009-06-15 22:36:37 -07001258 fn = os.path.join(sourcedir, "cmdline")
1259 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001260 cmd.append("--cmdline")
1261 cmd.append(open(fn).read().rstrip("\n"))
1262
1263 fn = os.path.join(sourcedir, "base")
1264 if os.access(fn, os.F_OK):
1265 cmd.append("--base")
1266 cmd.append(open(fn).read().rstrip("\n"))
1267
Ying Wang4de6b5b2010-08-25 14:29:34 -07001268 fn = os.path.join(sourcedir, "pagesize")
1269 if os.access(fn, os.F_OK):
1270 cmd.append("--pagesize")
1271 cmd.append(open(fn).read().rstrip("\n"))
1272
Steve Mucklef84668e2020-03-16 19:13:46 -07001273 if partition_name == "recovery":
1274 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301275 if not args:
1276 # Fall back to "mkbootimg_args" for recovery image
1277 # in case "recovery_mkbootimg_args" is not set.
1278 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001279 else:
1280 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001281 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001282 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001283
Tao Bao76def242017-11-21 09:25:31 -08001284 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001285 if args and args.strip():
1286 cmd.extend(shlex.split(args))
1287
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001288 if has_ramdisk:
1289 cmd.extend(["--ramdisk", ramdisk_img.name])
1290
Tao Baod95e9fd2015-03-29 23:07:41 -07001291 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001292 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001293 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001294 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001295 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001296 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001297
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001298 if partition_name == "recovery":
1299 if info_dict.get("include_recovery_dtbo") == "true":
1300 fn = os.path.join(sourcedir, "recovery_dtbo")
1301 cmd.extend(["--recovery_dtbo", fn])
1302 if info_dict.get("include_recovery_acpio") == "true":
1303 fn = os.path.join(sourcedir, "recovery_acpio")
1304 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001305
Tao Bao986ee862018-10-04 15:46:16 -07001306 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001307
Tao Bao76def242017-11-21 09:25:31 -08001308 if (info_dict.get("boot_signer") == "true" and
1309 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001310 # Hard-code the path as "/boot" for two-step special recovery image (which
1311 # will be loaded into /boot during the two-step OTA).
1312 if two_step_image:
1313 path = "/boot"
1314 else:
Tao Baobf70c312017-07-11 17:27:55 -07001315 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001316 cmd = [OPTIONS.boot_signer_path]
1317 cmd.extend(OPTIONS.boot_signer_args)
1318 cmd.extend([path, img.name,
1319 info_dict["verity_key"] + ".pk8",
1320 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001321 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001322
Tao Baod95e9fd2015-03-29 23:07:41 -07001323 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001324 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001325 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001326 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001327 # We have switched from the prebuilt futility binary to using the tool
1328 # (futility-host) built from the source. Override the setting in the old
1329 # TF.zip.
1330 futility = info_dict["futility"]
1331 if futility.startswith("prebuilts/"):
1332 futility = "futility-host"
1333 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001334 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001335 info_dict["vboot_key"] + ".vbprivk",
1336 info_dict["vboot_subkey"] + ".vbprivk",
1337 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001338 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001339 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001340
Tao Baof3282b42015-04-01 11:21:55 -07001341 # Clean up the temp files.
1342 img_unsigned.close()
1343 img_keyblock.close()
1344
David Zeuthen8fecb282017-12-01 16:24:01 -05001345 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001346 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001347 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001348 if partition_name == "recovery":
1349 part_size = info_dict["recovery_size"]
1350 else:
1351 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001352 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001353 "--partition_size", str(part_size), "--partition_name",
1354 partition_name]
1355 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001356 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001357 if args and args.strip():
1358 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001359 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001360
1361 img.seek(os.SEEK_SET, 0)
1362 data = img.read()
1363
1364 if has_ramdisk:
1365 ramdisk_img.close()
1366 img.close()
1367
1368 return data
1369
1370
Doug Zongkerd5131602012-08-02 14:46:42 -07001371def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001372 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001373 """Return a File object with the desired bootable image.
1374
1375 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1376 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1377 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001378
Doug Zongker55d93282011-01-25 17:03:34 -08001379 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1380 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001381 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001382 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001383
1384 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1385 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001386 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001387 return File.FromLocalFile(name, prebuilt_path)
1388
Tao Bao32fcdab2018-10-12 10:30:39 -07001389 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001390
1391 if info_dict is None:
1392 info_dict = OPTIONS.info_dict
1393
1394 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001395 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1396 # for recovery.
1397 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1398 prebuilt_name != "boot.img" or
1399 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001400
Doug Zongker6f1d0312014-08-22 08:07:12 -07001401 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001402 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001403 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001404 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001405 if data:
1406 return File(name, data)
1407 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001408
Doug Zongkereef39442009-04-02 12:14:19 -07001409
Steve Mucklee1b10862019-07-10 10:49:37 -07001410def _BuildVendorBootImage(sourcedir, info_dict=None):
1411 """Build a vendor boot image from the specified sourcedir.
1412
1413 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1414 turn them into a vendor boot image.
1415
1416 Return the image data, or None if sourcedir does not appear to contains files
1417 for building the requested image.
1418 """
1419
1420 if info_dict is None:
1421 info_dict = OPTIONS.info_dict
1422
1423 img = tempfile.NamedTemporaryFile()
1424
1425 ramdisk_img = _MakeRamdisk(sourcedir)
1426
1427 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1428 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1429
1430 cmd = [mkbootimg]
1431
1432 fn = os.path.join(sourcedir, "dtb")
1433 if os.access(fn, os.F_OK):
1434 cmd.append("--dtb")
1435 cmd.append(fn)
1436
1437 fn = os.path.join(sourcedir, "vendor_cmdline")
1438 if os.access(fn, os.F_OK):
1439 cmd.append("--vendor_cmdline")
1440 cmd.append(open(fn).read().rstrip("\n"))
1441
1442 fn = os.path.join(sourcedir, "base")
1443 if os.access(fn, os.F_OK):
1444 cmd.append("--base")
1445 cmd.append(open(fn).read().rstrip("\n"))
1446
1447 fn = os.path.join(sourcedir, "pagesize")
1448 if os.access(fn, os.F_OK):
1449 cmd.append("--pagesize")
1450 cmd.append(open(fn).read().rstrip("\n"))
1451
1452 args = info_dict.get("mkbootimg_args")
1453 if args and args.strip():
1454 cmd.extend(shlex.split(args))
1455
1456 args = info_dict.get("mkbootimg_version_args")
1457 if args and args.strip():
1458 cmd.extend(shlex.split(args))
1459
1460 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1461 cmd.extend(["--vendor_boot", img.name])
1462
1463 RunAndCheckOutput(cmd)
1464
1465 # AVB: if enabled, calculate and add hash.
1466 if info_dict.get("avb_enable") == "true":
1467 avbtool = info_dict["avb_avbtool"]
1468 part_size = info_dict["vendor_boot_size"]
1469 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001470 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001471 AppendAVBSigningArgs(cmd, "vendor_boot")
1472 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1473 if args and args.strip():
1474 cmd.extend(shlex.split(args))
1475 RunAndCheckOutput(cmd)
1476
1477 img.seek(os.SEEK_SET, 0)
1478 data = img.read()
1479
1480 ramdisk_img.close()
1481 img.close()
1482
1483 return data
1484
1485
1486def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1487 info_dict=None):
1488 """Return a File object with the desired vendor boot image.
1489
1490 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1491 the source files in 'unpack_dir'/'tree_subdir'."""
1492
1493 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1494 if os.path.exists(prebuilt_path):
1495 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1496 return File.FromLocalFile(name, prebuilt_path)
1497
1498 logger.info("building image from target_files %s...", tree_subdir)
1499
1500 if info_dict is None:
1501 info_dict = OPTIONS.info_dict
1502
1503 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1504 if data:
1505 return File(name, data)
1506 return None
1507
1508
Narayan Kamatha07bf042017-08-14 14:49:21 +01001509def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001510 """Gunzips the given gzip compressed file to a given output file."""
1511 with gzip.open(in_filename, "rb") as in_file, \
1512 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001513 shutil.copyfileobj(in_file, out_file)
1514
1515
Tao Bao0ff15de2019-03-20 11:26:06 -07001516def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001517 """Unzips the archive to the given directory.
1518
1519 Args:
1520 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001521 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001522 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1523 archvie. Non-matching patterns will be filtered out. If there's no match
1524 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001525 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001526 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001527 if patterns is not None:
1528 # Filter out non-matching patterns. unzip will complain otherwise.
1529 with zipfile.ZipFile(filename) as input_zip:
1530 names = input_zip.namelist()
1531 filtered = [
1532 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1533
1534 # There isn't any matching files. Don't unzip anything.
1535 if not filtered:
1536 return
1537 cmd.extend(filtered)
1538
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001539 RunAndCheckOutput(cmd)
1540
1541
Doug Zongker75f17362009-12-08 13:46:44 -08001542def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001543 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001544
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001545 Args:
1546 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1547 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1548
1549 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1550 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001551
Tao Bao1c830bf2017-12-25 10:43:47 -08001552 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001553 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001554 """
Doug Zongkereef39442009-04-02 12:14:19 -07001555
Tao Bao1c830bf2017-12-25 10:43:47 -08001556 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001557 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1558 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001559 UnzipToDir(m.group(1), tmp, pattern)
1560 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001561 filename = m.group(1)
1562 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001563 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001564
Tao Baodba59ee2018-01-09 13:21:02 -08001565 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001566
1567
Yifan Hong8a66a712019-04-04 15:37:57 -07001568def GetUserImage(which, tmpdir, input_zip,
1569 info_dict=None,
1570 allow_shared_blocks=None,
1571 hashtree_info_generator=None,
1572 reset_file_map=False):
1573 """Returns an Image object suitable for passing to BlockImageDiff.
1574
1575 This function loads the specified image from the given path. If the specified
1576 image is sparse, it also performs additional processing for OTA purpose. For
1577 example, it always adds block 0 to clobbered blocks list. It also detects
1578 files that cannot be reconstructed from the block list, for whom we should
1579 avoid applying imgdiff.
1580
1581 Args:
1582 which: The partition name.
1583 tmpdir: The directory that contains the prebuilt image and block map file.
1584 input_zip: The target-files ZIP archive.
1585 info_dict: The dict to be looked up for relevant info.
1586 allow_shared_blocks: If image is sparse, whether having shared blocks is
1587 allowed. If none, it is looked up from info_dict.
1588 hashtree_info_generator: If present and image is sparse, generates the
1589 hashtree_info for this sparse image.
1590 reset_file_map: If true and image is sparse, reset file map before returning
1591 the image.
1592 Returns:
1593 A Image object. If it is a sparse image and reset_file_map is False, the
1594 image will have file_map info loaded.
1595 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001596 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001597 info_dict = LoadInfoDict(input_zip)
1598
1599 is_sparse = info_dict.get("extfs_sparse_flag")
1600
1601 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1602 # shared blocks (i.e. some blocks will show up in multiple files' block
1603 # list). We can only allocate such shared blocks to the first "owner", and
1604 # disable imgdiff for all later occurrences.
1605 if allow_shared_blocks is None:
1606 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1607
1608 if is_sparse:
1609 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1610 hashtree_info_generator)
1611 if reset_file_map:
1612 img.ResetFileMap()
1613 return img
1614 else:
1615 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1616
1617
1618def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1619 """Returns a Image object suitable for passing to BlockImageDiff.
1620
1621 This function loads the specified non-sparse image from the given path.
1622
1623 Args:
1624 which: The partition name.
1625 tmpdir: The directory that contains the prebuilt image and block map file.
1626 Returns:
1627 A Image object.
1628 """
1629 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1630 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1631
1632 # The image and map files must have been created prior to calling
1633 # ota_from_target_files.py (since LMP).
1634 assert os.path.exists(path) and os.path.exists(mappath)
1635
Tianjie Xu41976c72019-07-03 13:57:01 -07001636 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1637
Yifan Hong8a66a712019-04-04 15:37:57 -07001638
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001639def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1640 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001641 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1642
1643 This function loads the specified sparse image from the given path, and
1644 performs additional processing for OTA purpose. For example, it always adds
1645 block 0 to clobbered blocks list. It also detects files that cannot be
1646 reconstructed from the block list, for whom we should avoid applying imgdiff.
1647
1648 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001649 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001650 tmpdir: The directory that contains the prebuilt image and block map file.
1651 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001652 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001653 hashtree_info_generator: If present, generates the hashtree_info for this
1654 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001655 Returns:
1656 A SparseImage object, with file_map info loaded.
1657 """
Tao Baoc765cca2018-01-31 17:32:40 -08001658 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1659 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1660
1661 # The image and map files must have been created prior to calling
1662 # ota_from_target_files.py (since LMP).
1663 assert os.path.exists(path) and os.path.exists(mappath)
1664
1665 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1666 # it to clobbered_blocks so that it will be written to the target
1667 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1668 clobbered_blocks = "0"
1669
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001670 image = sparse_img.SparseImage(
1671 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1672 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001673
1674 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1675 # if they contain all zeros. We can't reconstruct such a file from its block
1676 # list. Tag such entries accordingly. (Bug: 65213616)
1677 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001678 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001679 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001680 continue
1681
Tom Cherryd14b8952018-08-09 14:26:00 -07001682 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1683 # filename listed in system.map may contain an additional leading slash
1684 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1685 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001686 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001687
Tom Cherryd14b8952018-08-09 14:26:00 -07001688 # Special handling another case, where files not under /system
1689 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001690 if which == 'system' and not arcname.startswith('SYSTEM'):
1691 arcname = 'ROOT/' + arcname
1692
1693 assert arcname in input_zip.namelist(), \
1694 "Failed to find the ZIP entry for {}".format(entry)
1695
Tao Baoc765cca2018-01-31 17:32:40 -08001696 info = input_zip.getinfo(arcname)
1697 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001698
1699 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001700 # image, check the original block list to determine its completeness. Note
1701 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001702 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001703 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001704
Tao Baoc765cca2018-01-31 17:32:40 -08001705 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1706 ranges.extra['incomplete'] = True
1707
1708 return image
1709
1710
Doug Zongkereef39442009-04-02 12:14:19 -07001711def GetKeyPasswords(keylist):
1712 """Given a list of keys, prompt the user to enter passwords for
1713 those which require them. Return a {key: password} dict. password
1714 will be None if the key has no password."""
1715
Doug Zongker8ce7c252009-05-22 13:34:54 -07001716 no_passwords = []
1717 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001718 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001719 devnull = open("/dev/null", "w+b")
1720 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001721 # We don't need a password for things that aren't really keys.
1722 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001723 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001724 continue
1725
T.R. Fullhart37e10522013-03-18 10:31:26 -07001726 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001727 "-inform", "DER", "-nocrypt"],
1728 stdin=devnull.fileno(),
1729 stdout=devnull.fileno(),
1730 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001731 p.communicate()
1732 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001733 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001734 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001735 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001736 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1737 "-inform", "DER", "-passin", "pass:"],
1738 stdin=devnull.fileno(),
1739 stdout=devnull.fileno(),
1740 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001741 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001742 if p.returncode == 0:
1743 # Encrypted key with empty string as password.
1744 key_passwords[k] = ''
1745 elif stderr.startswith('Error decrypting key'):
1746 # Definitely encrypted key.
1747 # It would have said "Error reading key" if it didn't parse correctly.
1748 need_passwords.append(k)
1749 else:
1750 # Potentially, a type of key that openssl doesn't understand.
1751 # We'll let the routines in signapk.jar handle it.
1752 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001753 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001754
T.R. Fullhart37e10522013-03-18 10:31:26 -07001755 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001756 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001757 return key_passwords
1758
1759
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001760def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001761 """Gets the minSdkVersion declared in the APK.
1762
changho.shin0f125362019-07-08 10:59:00 +09001763 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001764 This can be both a decimal number (API Level) or a codename.
1765
1766 Args:
1767 apk_name: The APK filename.
1768
1769 Returns:
1770 The parsed SDK version string.
1771
1772 Raises:
1773 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001774 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001775 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001776 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001777 stderr=subprocess.PIPE)
1778 stdoutdata, stderrdata = proc.communicate()
1779 if proc.returncode != 0:
1780 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001781 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001782 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001783
Tao Baof47bf0f2018-03-21 23:28:51 -07001784 for line in stdoutdata.split("\n"):
1785 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001786 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1787 if m:
1788 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001789 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001790
1791
1792def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001793 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001794
Tao Baof47bf0f2018-03-21 23:28:51 -07001795 If minSdkVersion is set to a codename, it is translated to a number using the
1796 provided map.
1797
1798 Args:
1799 apk_name: The APK filename.
1800
1801 Returns:
1802 The parsed SDK version number.
1803
1804 Raises:
1805 ExternalError: On failing to get the min SDK version number.
1806 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001807 version = GetMinSdkVersion(apk_name)
1808 try:
1809 return int(version)
1810 except ValueError:
1811 # Not a decimal number. Codename?
1812 if version in codename_to_api_level_map:
1813 return codename_to_api_level_map[version]
1814 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001815 raise ExternalError(
1816 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1817 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001818
1819
1820def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001821 codename_to_api_level_map=None, whole_file=False,
1822 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001823 """Sign the input_name zip/jar/apk, producing output_name. Use the
1824 given key and password (the latter may be None if the key does not
1825 have a password.
1826
Doug Zongker951495f2009-08-14 12:44:19 -07001827 If whole_file is true, use the "-w" option to SignApk to embed a
1828 signature that covers the whole file in the archive comment of the
1829 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001830
1831 min_api_level is the API Level (int) of the oldest platform this file may end
1832 up on. If not specified for an APK, the API Level is obtained by interpreting
1833 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1834
1835 codename_to_api_level_map is needed to translate the codename which may be
1836 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001837
1838 Caller may optionally specify extra args to be passed to SignApk, which
1839 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001840 """
Tao Bao76def242017-11-21 09:25:31 -08001841 if codename_to_api_level_map is None:
1842 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001843 if extra_signapk_args is None:
1844 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001845
Alex Klyubin9667b182015-12-10 13:38:50 -08001846 java_library_path = os.path.join(
1847 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1848
Tao Baoe95540e2016-11-08 12:08:53 -08001849 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1850 ["-Djava.library.path=" + java_library_path,
1851 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001852 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001853 if whole_file:
1854 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001855
1856 min_sdk_version = min_api_level
1857 if min_sdk_version is None:
1858 if not whole_file:
1859 min_sdk_version = GetMinSdkVersionInt(
1860 input_name, codename_to_api_level_map)
1861 if min_sdk_version is not None:
1862 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1863
T.R. Fullhart37e10522013-03-18 10:31:26 -07001864 cmd.extend([key + OPTIONS.public_key_suffix,
1865 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001866 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001867
Tao Bao73dd4f42018-10-04 16:25:33 -07001868 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001869 if password is not None:
1870 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001871 stdoutdata, _ = proc.communicate(password)
1872 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001873 raise ExternalError(
1874 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001875 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001876
Doug Zongkereef39442009-04-02 12:14:19 -07001877
Doug Zongker37974732010-09-16 17:44:38 -07001878def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001879 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001880
Tao Bao9dd909e2017-11-14 11:27:32 -08001881 For non-AVB images, raise exception if the data is too big. Print a warning
1882 if the data is nearing the maximum size.
1883
1884 For AVB images, the actual image size should be identical to the limit.
1885
1886 Args:
1887 data: A string that contains all the data for the partition.
1888 target: The partition name. The ".img" suffix is optional.
1889 info_dict: The dict to be looked up for relevant info.
1890 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001891 if target.endswith(".img"):
1892 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001893 mount_point = "/" + target
1894
Ying Wangf8824af2014-06-03 14:07:27 -07001895 fs_type = None
1896 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001897 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001898 if mount_point == "/userdata":
1899 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001900 p = info_dict["fstab"][mount_point]
1901 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001902 device = p.device
1903 if "/" in device:
1904 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001905 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001906 if not fs_type or not limit:
1907 return
Doug Zongkereef39442009-04-02 12:14:19 -07001908
Andrew Boie0f9aec82012-02-14 09:32:52 -08001909 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001910 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1911 # path.
1912 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1913 if size != limit:
1914 raise ExternalError(
1915 "Mismatching image size for %s: expected %d actual %d" % (
1916 target, limit, size))
1917 else:
1918 pct = float(size) * 100.0 / limit
1919 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1920 if pct >= 99.0:
1921 raise ExternalError(msg)
1922 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001923 logger.warning("\n WARNING: %s\n", msg)
1924 else:
1925 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001926
1927
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001928def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001929 """Parses the APK certs info from a given target-files zip.
1930
1931 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1932 tuple with the following elements: (1) a dictionary that maps packages to
1933 certs (based on the "certificate" and "private_key" attributes in the file;
1934 (2) a string representing the extension of compressed APKs in the target files
1935 (e.g ".gz", ".bro").
1936
1937 Args:
1938 tf_zip: The input target_files ZipFile (already open).
1939
1940 Returns:
1941 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1942 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1943 no compressed APKs.
1944 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001945 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001946 compressed_extension = None
1947
Tao Bao0f990332017-09-08 19:02:54 -07001948 # META/apkcerts.txt contains the info for _all_ the packages known at build
1949 # time. Filter out the ones that are not installed.
1950 installed_files = set()
1951 for name in tf_zip.namelist():
1952 basename = os.path.basename(name)
1953 if basename:
1954 installed_files.add(basename)
1955
Tao Baoda30cfa2017-12-01 16:19:46 -08001956 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001957 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001958 if not line:
1959 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001960 m = re.match(
1961 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001962 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1963 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001964 line)
1965 if not m:
1966 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001967
Tao Bao818ddf52018-01-05 11:17:34 -08001968 matches = m.groupdict()
1969 cert = matches["CERT"]
1970 privkey = matches["PRIVKEY"]
1971 name = matches["NAME"]
1972 this_compressed_extension = matches["COMPRESSED"]
1973
1974 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1975 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1976 if cert in SPECIAL_CERT_STRINGS and not privkey:
1977 certmap[name] = cert
1978 elif (cert.endswith(OPTIONS.public_key_suffix) and
1979 privkey.endswith(OPTIONS.private_key_suffix) and
1980 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1981 certmap[name] = cert[:-public_key_suffix_len]
1982 else:
1983 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1984
1985 if not this_compressed_extension:
1986 continue
1987
1988 # Only count the installed files.
1989 filename = name + '.' + this_compressed_extension
1990 if filename not in installed_files:
1991 continue
1992
1993 # Make sure that all the values in the compression map have the same
1994 # extension. We don't support multiple compression methods in the same
1995 # system image.
1996 if compressed_extension:
1997 if this_compressed_extension != compressed_extension:
1998 raise ValueError(
1999 "Multiple compressed extensions: {} vs {}".format(
2000 compressed_extension, this_compressed_extension))
2001 else:
2002 compressed_extension = this_compressed_extension
2003
2004 return (certmap,
2005 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002006
2007
Doug Zongkereef39442009-04-02 12:14:19 -07002008COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002009Global options
2010
2011 -p (--path) <dir>
2012 Prepend <dir>/bin to the list of places to search for binaries run by this
2013 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002014
Doug Zongker05d3dea2009-06-22 11:32:31 -07002015 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002016 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002017
Tao Bao30df8b42018-04-23 15:32:53 -07002018 -x (--extra) <key=value>
2019 Add a key/value pair to the 'extras' dict, which device-specific extension
2020 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002021
Doug Zongkereef39442009-04-02 12:14:19 -07002022 -v (--verbose)
2023 Show command lines being executed.
2024
2025 -h (--help)
2026 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002027
2028 --logfile <file>
2029 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002030"""
2031
2032def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002033 print(docstring.rstrip("\n"))
2034 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002035
2036
2037def ParseOptions(argv,
2038 docstring,
2039 extra_opts="", extra_long_opts=(),
2040 extra_option_handler=None):
2041 """Parse the options in argv and return any arguments that aren't
2042 flags. docstring is the calling module's docstring, to be displayed
2043 for errors and -h. extra_opts and extra_long_opts are for flags
2044 defined by the caller, which are processed by passing them to
2045 extra_option_handler."""
2046
2047 try:
2048 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002049 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002050 ["help", "verbose", "path=", "signapk_path=",
2051 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002052 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002053 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2054 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002055 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2056 "aftl_key_path=", "aftl_manufacturer_key_path=",
2057 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002058 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002059 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002060 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002061 sys.exit(2)
2062
Doug Zongkereef39442009-04-02 12:14:19 -07002063 for o, a in opts:
2064 if o in ("-h", "--help"):
2065 Usage(docstring)
2066 sys.exit()
2067 elif o in ("-v", "--verbose"):
2068 OPTIONS.verbose = True
2069 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002070 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002071 elif o in ("--signapk_path",):
2072 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002073 elif o in ("--signapk_shared_library_path",):
2074 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002075 elif o in ("--extra_signapk_args",):
2076 OPTIONS.extra_signapk_args = shlex.split(a)
2077 elif o in ("--java_path",):
2078 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002079 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002080 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002081 elif o in ("--android_jar_path",):
2082 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002083 elif o in ("--public_key_suffix",):
2084 OPTIONS.public_key_suffix = a
2085 elif o in ("--private_key_suffix",):
2086 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002087 elif o in ("--boot_signer_path",):
2088 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002089 elif o in ("--boot_signer_args",):
2090 OPTIONS.boot_signer_args = shlex.split(a)
2091 elif o in ("--verity_signer_path",):
2092 OPTIONS.verity_signer_path = a
2093 elif o in ("--verity_signer_args",):
2094 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002095 elif o in ("--aftl_tool_path",):
2096 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002097 elif o in ("--aftl_server",):
2098 OPTIONS.aftl_server = a
2099 elif o in ("--aftl_key_path",):
2100 OPTIONS.aftl_key_path = a
2101 elif o in ("--aftl_manufacturer_key_path",):
2102 OPTIONS.aftl_manufacturer_key_path = a
2103 elif o in ("--aftl_signer_helper",):
2104 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002105 elif o in ("-s", "--device_specific"):
2106 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002107 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002108 key, value = a.split("=", 1)
2109 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002110 elif o in ("--logfile",):
2111 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002112 else:
2113 if extra_option_handler is None or not extra_option_handler(o, a):
2114 assert False, "unknown option \"%s\"" % (o,)
2115
Doug Zongker85448772014-09-09 14:59:20 -07002116 if OPTIONS.search_path:
2117 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2118 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002119
2120 return args
2121
2122
Tao Bao4c851b12016-09-19 13:54:38 -07002123def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002124 """Make a temp file and add it to the list of things to be deleted
2125 when Cleanup() is called. Return the filename."""
2126 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2127 os.close(fd)
2128 OPTIONS.tempfiles.append(fn)
2129 return fn
2130
2131
Tao Bao1c830bf2017-12-25 10:43:47 -08002132def MakeTempDir(prefix='tmp', suffix=''):
2133 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2134
2135 Returns:
2136 The absolute pathname of the new directory.
2137 """
2138 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2139 OPTIONS.tempfiles.append(dir_name)
2140 return dir_name
2141
2142
Doug Zongkereef39442009-04-02 12:14:19 -07002143def Cleanup():
2144 for i in OPTIONS.tempfiles:
2145 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002146 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002147 else:
2148 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002149 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002150
2151
2152class PasswordManager(object):
2153 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002154 self.editor = os.getenv("EDITOR")
2155 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002156
2157 def GetPasswords(self, items):
2158 """Get passwords corresponding to each string in 'items',
2159 returning a dict. (The dict may have keys in addition to the
2160 values in 'items'.)
2161
2162 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2163 user edit that file to add more needed passwords. If no editor is
2164 available, or $ANDROID_PW_FILE isn't define, prompts the user
2165 interactively in the ordinary way.
2166 """
2167
2168 current = self.ReadFile()
2169
2170 first = True
2171 while True:
2172 missing = []
2173 for i in items:
2174 if i not in current or not current[i]:
2175 missing.append(i)
2176 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002177 if not missing:
2178 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002179
2180 for i in missing:
2181 current[i] = ""
2182
2183 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002184 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002185 if sys.version_info[0] >= 3:
2186 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002187 answer = raw_input("try to edit again? [y]> ").strip()
2188 if answer and answer[0] not in 'yY':
2189 raise RuntimeError("key passwords unavailable")
2190 first = False
2191
2192 current = self.UpdateAndReadFile(current)
2193
Dan Albert8b72aef2015-03-23 19:13:21 -07002194 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002195 """Prompt the user to enter a value (password) for each key in
2196 'current' whose value is fales. Returns a new dict with all the
2197 values.
2198 """
2199 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002200 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002201 if v:
2202 result[k] = v
2203 else:
2204 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 result[k] = getpass.getpass(
2206 "Enter password for %s key> " % k).strip()
2207 if result[k]:
2208 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002209 return result
2210
2211 def UpdateAndReadFile(self, current):
2212 if not self.editor or not self.pwfile:
2213 return self.PromptResult(current)
2214
2215 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002216 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002217 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2218 f.write("# (Additional spaces are harmless.)\n\n")
2219
2220 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002221 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002222 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002223 f.write("[[[ %s ]]] %s\n" % (v, k))
2224 if not v and first_line is None:
2225 # position cursor on first line with no password.
2226 first_line = i + 4
2227 f.close()
2228
Tao Bao986ee862018-10-04 15:46:16 -07002229 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002230
2231 return self.ReadFile()
2232
2233 def ReadFile(self):
2234 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002235 if self.pwfile is None:
2236 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002237 try:
2238 f = open(self.pwfile, "r")
2239 for line in f:
2240 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002241 if not line or line[0] == '#':
2242 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002243 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2244 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002245 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002246 else:
2247 result[m.group(2)] = m.group(1)
2248 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002249 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002250 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002251 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002252 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002253
2254
Dan Albert8e0178d2015-01-27 15:53:15 -08002255def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2256 compress_type=None):
2257 import datetime
2258
2259 # http://b/18015246
2260 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2261 # for files larger than 2GiB. We can work around this by adjusting their
2262 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2263 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2264 # it isn't clear to me exactly what circumstances cause this).
2265 # `zipfile.write()` must be used directly to work around this.
2266 #
2267 # This mess can be avoided if we port to python3.
2268 saved_zip64_limit = zipfile.ZIP64_LIMIT
2269 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2270
2271 if compress_type is None:
2272 compress_type = zip_file.compression
2273 if arcname is None:
2274 arcname = filename
2275
2276 saved_stat = os.stat(filename)
2277
2278 try:
2279 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2280 # file to be zipped and reset it when we're done.
2281 os.chmod(filename, perms)
2282
2283 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002284 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2285 # intentional. zip stores datetimes in local time without a time zone
2286 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2287 # in the zip archive.
2288 local_epoch = datetime.datetime.fromtimestamp(0)
2289 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002290 os.utime(filename, (timestamp, timestamp))
2291
2292 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2293 finally:
2294 os.chmod(filename, saved_stat.st_mode)
2295 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2296 zipfile.ZIP64_LIMIT = saved_zip64_limit
2297
2298
Tao Bao58c1b962015-05-20 09:32:18 -07002299def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002300 compress_type=None):
2301 """Wrap zipfile.writestr() function to work around the zip64 limit.
2302
2303 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2304 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2305 when calling crc32(bytes).
2306
2307 But it still works fine to write a shorter string into a large zip file.
2308 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2309 when we know the string won't be too long.
2310 """
2311
2312 saved_zip64_limit = zipfile.ZIP64_LIMIT
2313 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2314
2315 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2316 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002317 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002318 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002319 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002320 else:
Tao Baof3282b42015-04-01 11:21:55 -07002321 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002322 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2323 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2324 # such a case (since
2325 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2326 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2327 # permission bits. We follow the logic in Python 3 to get consistent
2328 # behavior between using the two versions.
2329 if not zinfo.external_attr:
2330 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002331
2332 # If compress_type is given, it overrides the value in zinfo.
2333 if compress_type is not None:
2334 zinfo.compress_type = compress_type
2335
Tao Bao58c1b962015-05-20 09:32:18 -07002336 # If perms is given, it has a priority.
2337 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002338 # If perms doesn't set the file type, mark it as a regular file.
2339 if perms & 0o770000 == 0:
2340 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002341 zinfo.external_attr = perms << 16
2342
Tao Baof3282b42015-04-01 11:21:55 -07002343 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002344 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2345
Dan Albert8b72aef2015-03-23 19:13:21 -07002346 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002347 zipfile.ZIP64_LIMIT = saved_zip64_limit
2348
2349
Tao Bao89d7ab22017-12-14 17:05:33 -08002350def ZipDelete(zip_filename, entries):
2351 """Deletes entries from a ZIP file.
2352
2353 Since deleting entries from a ZIP file is not supported, it shells out to
2354 'zip -d'.
2355
2356 Args:
2357 zip_filename: The name of the ZIP file.
2358 entries: The name of the entry, or the list of names to be deleted.
2359
2360 Raises:
2361 AssertionError: In case of non-zero return from 'zip'.
2362 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002363 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002364 entries = [entries]
2365 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002366 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002367
2368
Tao Baof3282b42015-04-01 11:21:55 -07002369def ZipClose(zip_file):
2370 # http://b/18015246
2371 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2372 # central directory.
2373 saved_zip64_limit = zipfile.ZIP64_LIMIT
2374 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2375
2376 zip_file.close()
2377
2378 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002379
2380
2381class DeviceSpecificParams(object):
2382 module = None
2383 def __init__(self, **kwargs):
2384 """Keyword arguments to the constructor become attributes of this
2385 object, which is passed to all functions in the device-specific
2386 module."""
Tao Bao38884282019-07-10 22:20:56 -07002387 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002388 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002389 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002390
2391 if self.module is None:
2392 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002393 if not path:
2394 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002395 try:
2396 if os.path.isdir(path):
2397 info = imp.find_module("releasetools", [path])
2398 else:
2399 d, f = os.path.split(path)
2400 b, x = os.path.splitext(f)
2401 if x == ".py":
2402 f = b
2403 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002404 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002405 self.module = imp.load_module("device_specific", *info)
2406 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002407 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002408
2409 def _DoCall(self, function_name, *args, **kwargs):
2410 """Call the named function in the device-specific module, passing
2411 the given args and kwargs. The first argument to the call will be
2412 the DeviceSpecific object itself. If there is no module, or the
2413 module does not define the function, return the value of the
2414 'default' kwarg (which itself defaults to None)."""
2415 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002416 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002417 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2418
2419 def FullOTA_Assertions(self):
2420 """Called after emitting the block of assertions at the top of a
2421 full OTA package. Implementations can add whatever additional
2422 assertions they like."""
2423 return self._DoCall("FullOTA_Assertions")
2424
Doug Zongkere5ff5902012-01-17 10:55:37 -08002425 def FullOTA_InstallBegin(self):
2426 """Called at the start of full OTA installation."""
2427 return self._DoCall("FullOTA_InstallBegin")
2428
Yifan Hong10c530d2018-12-27 17:34:18 -08002429 def FullOTA_GetBlockDifferences(self):
2430 """Called during full OTA installation and verification.
2431 Implementation should return a list of BlockDifference objects describing
2432 the update on each additional partitions.
2433 """
2434 return self._DoCall("FullOTA_GetBlockDifferences")
2435
Doug Zongker05d3dea2009-06-22 11:32:31 -07002436 def FullOTA_InstallEnd(self):
2437 """Called at the end of full OTA installation; typically this is
2438 used to install the image for the device's baseband processor."""
2439 return self._DoCall("FullOTA_InstallEnd")
2440
2441 def IncrementalOTA_Assertions(self):
2442 """Called after emitting the block of assertions at the top of an
2443 incremental OTA package. Implementations can add whatever
2444 additional assertions they like."""
2445 return self._DoCall("IncrementalOTA_Assertions")
2446
Doug Zongkere5ff5902012-01-17 10:55:37 -08002447 def IncrementalOTA_VerifyBegin(self):
2448 """Called at the start of the verification phase of incremental
2449 OTA installation; additional checks can be placed here to abort
2450 the script before any changes are made."""
2451 return self._DoCall("IncrementalOTA_VerifyBegin")
2452
Doug Zongker05d3dea2009-06-22 11:32:31 -07002453 def IncrementalOTA_VerifyEnd(self):
2454 """Called at the end of the verification phase of incremental OTA
2455 installation; additional checks can be placed here to abort the
2456 script before any changes are made."""
2457 return self._DoCall("IncrementalOTA_VerifyEnd")
2458
Doug Zongkere5ff5902012-01-17 10:55:37 -08002459 def IncrementalOTA_InstallBegin(self):
2460 """Called at the start of incremental OTA installation (after
2461 verification is complete)."""
2462 return self._DoCall("IncrementalOTA_InstallBegin")
2463
Yifan Hong10c530d2018-12-27 17:34:18 -08002464 def IncrementalOTA_GetBlockDifferences(self):
2465 """Called during incremental OTA installation and verification.
2466 Implementation should return a list of BlockDifference objects describing
2467 the update on each additional partitions.
2468 """
2469 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2470
Doug Zongker05d3dea2009-06-22 11:32:31 -07002471 def IncrementalOTA_InstallEnd(self):
2472 """Called at the end of incremental OTA installation; typically
2473 this is used to install the image for the device's baseband
2474 processor."""
2475 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002476
Tao Bao9bc6bb22015-11-09 16:58:28 -08002477 def VerifyOTA_Assertions(self):
2478 return self._DoCall("VerifyOTA_Assertions")
2479
Tao Bao76def242017-11-21 09:25:31 -08002480
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002481class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002482 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002483 self.name = name
2484 self.data = data
2485 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002486 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002487 self.sha1 = sha1(data).hexdigest()
2488
2489 @classmethod
2490 def FromLocalFile(cls, name, diskname):
2491 f = open(diskname, "rb")
2492 data = f.read()
2493 f.close()
2494 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002495
2496 def WriteToTemp(self):
2497 t = tempfile.NamedTemporaryFile()
2498 t.write(self.data)
2499 t.flush()
2500 return t
2501
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002502 def WriteToDir(self, d):
2503 with open(os.path.join(d, self.name), "wb") as fp:
2504 fp.write(self.data)
2505
Geremy Condra36bd3652014-02-06 19:45:10 -08002506 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002507 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002508
Tao Bao76def242017-11-21 09:25:31 -08002509
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002510DIFF_PROGRAM_BY_EXT = {
2511 ".gz" : "imgdiff",
2512 ".zip" : ["imgdiff", "-z"],
2513 ".jar" : ["imgdiff", "-z"],
2514 ".apk" : ["imgdiff", "-z"],
2515 ".img" : "imgdiff",
2516 }
2517
Tao Bao76def242017-11-21 09:25:31 -08002518
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002520 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002521 self.tf = tf
2522 self.sf = sf
2523 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002524 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002525
2526 def ComputePatch(self):
2527 """Compute the patch (as a string of data) needed to turn sf into
2528 tf. Returns the same tuple as GetPatch()."""
2529
2530 tf = self.tf
2531 sf = self.sf
2532
Doug Zongker24cd2802012-08-14 16:36:15 -07002533 if self.diff_program:
2534 diff_program = self.diff_program
2535 else:
2536 ext = os.path.splitext(tf.name)[1]
2537 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002538
2539 ttemp = tf.WriteToTemp()
2540 stemp = sf.WriteToTemp()
2541
2542 ext = os.path.splitext(tf.name)[1]
2543
2544 try:
2545 ptemp = tempfile.NamedTemporaryFile()
2546 if isinstance(diff_program, list):
2547 cmd = copy.copy(diff_program)
2548 else:
2549 cmd = [diff_program]
2550 cmd.append(stemp.name)
2551 cmd.append(ttemp.name)
2552 cmd.append(ptemp.name)
2553 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002554 err = []
2555 def run():
2556 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002557 if e:
2558 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002559 th = threading.Thread(target=run)
2560 th.start()
2561 th.join(timeout=300) # 5 mins
2562 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002563 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002564 p.terminate()
2565 th.join(5)
2566 if th.is_alive():
2567 p.kill()
2568 th.join()
2569
Tianjie Xua2a9f992018-01-05 15:15:54 -08002570 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002571 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002572 self.patch = None
2573 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002574 diff = ptemp.read()
2575 finally:
2576 ptemp.close()
2577 stemp.close()
2578 ttemp.close()
2579
2580 self.patch = diff
2581 return self.tf, self.sf, self.patch
2582
2583
2584 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002585 """Returns a tuple of (target_file, source_file, patch_data).
2586
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002587 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002588 computing the patch failed.
2589 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002590 return self.tf, self.sf, self.patch
2591
2592
2593def ComputeDifferences(diffs):
2594 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002595 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002596
2597 # Do the largest files first, to try and reduce the long-pole effect.
2598 by_size = [(i.tf.size, i) for i in diffs]
2599 by_size.sort(reverse=True)
2600 by_size = [i[1] for i in by_size]
2601
2602 lock = threading.Lock()
2603 diff_iter = iter(by_size) # accessed under lock
2604
2605 def worker():
2606 try:
2607 lock.acquire()
2608 for d in diff_iter:
2609 lock.release()
2610 start = time.time()
2611 d.ComputePatch()
2612 dur = time.time() - start
2613 lock.acquire()
2614
2615 tf, sf, patch = d.GetPatch()
2616 if sf.name == tf.name:
2617 name = tf.name
2618 else:
2619 name = "%s (%s)" % (tf.name, sf.name)
2620 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002621 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002622 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002623 logger.info(
2624 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2625 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002626 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002627 except Exception:
2628 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002629 raise
2630
2631 # start worker threads; wait for them all to finish.
2632 threads = [threading.Thread(target=worker)
2633 for i in range(OPTIONS.worker_threads)]
2634 for th in threads:
2635 th.start()
2636 while threads:
2637 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002638
2639
Dan Albert8b72aef2015-03-23 19:13:21 -07002640class BlockDifference(object):
2641 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002642 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002643 self.tgt = tgt
2644 self.src = src
2645 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002646 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002647 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002648
Tao Baodd2a5892015-03-12 12:32:37 -07002649 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002650 version = max(
2651 int(i) for i in
2652 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002653 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002654 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002655
Tianjie Xu41976c72019-07-03 13:57:01 -07002656 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2657 version=self.version,
2658 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002659 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002660 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002661 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002662 self.touched_src_ranges = b.touched_src_ranges
2663 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002664
Yifan Hong10c530d2018-12-27 17:34:18 -08002665 # On devices with dynamic partitions, for new partitions,
2666 # src is None but OPTIONS.source_info_dict is not.
2667 if OPTIONS.source_info_dict is None:
2668 is_dynamic_build = OPTIONS.info_dict.get(
2669 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002670 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002671 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002672 is_dynamic_build = OPTIONS.source_info_dict.get(
2673 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002674 is_dynamic_source = partition in shlex.split(
2675 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002676
Yifan Hongbb2658d2019-01-25 12:30:58 -08002677 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002678 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2679
Yifan Hongbb2658d2019-01-25 12:30:58 -08002680 # For dynamic partitions builds, check partition list in both source
2681 # and target build because new partitions may be added, and existing
2682 # partitions may be removed.
2683 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2684
Yifan Hong10c530d2018-12-27 17:34:18 -08002685 if is_dynamic:
2686 self.device = 'map_partition("%s")' % partition
2687 else:
2688 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002689 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2690 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002691 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002692 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2693 OPTIONS.source_info_dict)
2694 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002695
Tao Baod8d14be2016-02-04 14:26:02 -08002696 @property
2697 def required_cache(self):
2698 return self._required_cache
2699
Tao Bao76def242017-11-21 09:25:31 -08002700 def WriteScript(self, script, output_zip, progress=None,
2701 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002702 if not self.src:
2703 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002704 script.Print("Patching %s image unconditionally..." % (self.partition,))
2705 else:
2706 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002707
Dan Albert8b72aef2015-03-23 19:13:21 -07002708 if progress:
2709 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002710 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002711
2712 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002713 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002714
Tao Bao9bc6bb22015-11-09 16:58:28 -08002715 def WriteStrictVerifyScript(self, script):
2716 """Verify all the blocks in the care_map, including clobbered blocks.
2717
2718 This differs from the WriteVerifyScript() function: a) it prints different
2719 error messages; b) it doesn't allow half-way updated images to pass the
2720 verification."""
2721
2722 partition = self.partition
2723 script.Print("Verifying %s..." % (partition,))
2724 ranges = self.tgt.care_map
2725 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002726 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002727 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2728 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002729 self.device, ranges_str,
2730 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002731 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002732 script.AppendExtra("")
2733
Tao Baod522bdc2016-04-12 15:53:16 -07002734 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002735 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002736
2737 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002738 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002739 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002740
2741 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002742 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002743 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002744 ranges = self.touched_src_ranges
2745 expected_sha1 = self.touched_src_sha1
2746 else:
2747 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2748 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002749
2750 # No blocks to be checked, skipping.
2751 if not ranges:
2752 return
2753
Tao Bao5ece99d2015-05-12 11:42:31 -07002754 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002755 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002756 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002757 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2758 '"%s.patch.dat")) then' % (
2759 self.device, ranges_str, expected_sha1,
2760 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002761 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002762 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002763
Tianjie Xufc3422a2015-12-15 11:53:59 -08002764 if self.version >= 4:
2765
2766 # Bug: 21124327
2767 # When generating incrementals for the system and vendor partitions in
2768 # version 4 or newer, explicitly check the first block (which contains
2769 # the superblock) of the partition to see if it's what we expect. If
2770 # this check fails, give an explicit log message about the partition
2771 # having been remounted R/W (the most likely explanation).
2772 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002773 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002774
2775 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002776 if partition == "system":
2777 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2778 else:
2779 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002780 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002781 'ifelse (block_image_recover({device}, "{ranges}") && '
2782 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002783 'package_extract_file("{partition}.transfer.list"), '
2784 '"{partition}.new.dat", "{partition}.patch.dat"), '
2785 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002786 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002787 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002788 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002789
Tao Baodd2a5892015-03-12 12:32:37 -07002790 # Abort the OTA update. Note that the incremental OTA cannot be applied
2791 # even if it may match the checksum of the target partition.
2792 # a) If version < 3, operations like move and erase will make changes
2793 # unconditionally and damage the partition.
2794 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002795 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002796 if partition == "system":
2797 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2798 else:
2799 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2800 script.AppendExtra((
2801 'abort("E%d: %s partition has unexpected contents");\n'
2802 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002803
Yifan Hong10c530d2018-12-27 17:34:18 -08002804 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002805 partition = self.partition
2806 script.Print('Verifying the updated %s image...' % (partition,))
2807 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2808 ranges = self.tgt.care_map
2809 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002810 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002811 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002812 self.device, ranges_str,
2813 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002814
2815 # Bug: 20881595
2816 # Verify that extended blocks are really zeroed out.
2817 if self.tgt.extended:
2818 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002819 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002820 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002821 self.device, ranges_str,
2822 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002823 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002824 if partition == "system":
2825 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2826 else:
2827 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002828 script.AppendExtra(
2829 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002830 ' abort("E%d: %s partition has unexpected non-zero contents after '
2831 'OTA update");\n'
2832 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002833 else:
2834 script.Print('Verified the updated %s image.' % (partition,))
2835
Tianjie Xu209db462016-05-24 17:34:52 -07002836 if partition == "system":
2837 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2838 else:
2839 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2840
Tao Bao5fcaaef2015-06-01 13:40:49 -07002841 script.AppendExtra(
2842 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002843 ' abort("E%d: %s partition has unexpected contents after OTA '
2844 'update");\n'
2845 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002846
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002847 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002848 ZipWrite(output_zip,
2849 '{}.transfer.list'.format(self.path),
2850 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002851
Tao Bao76def242017-11-21 09:25:31 -08002852 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2853 # its size. Quailty 9 almost triples the compression time but doesn't
2854 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002855 # zip | brotli(quality 6) | brotli(quality 9)
2856 # compressed_size: 942M | 869M (~8% reduced) | 854M
2857 # compression_time: 75s | 265s | 719s
2858 # decompression_time: 15s | 25s | 25s
2859
2860 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002861 brotli_cmd = ['brotli', '--quality=6',
2862 '--output={}.new.dat.br'.format(self.path),
2863 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002864 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002865 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002866
2867 new_data_name = '{}.new.dat.br'.format(self.partition)
2868 ZipWrite(output_zip,
2869 '{}.new.dat.br'.format(self.path),
2870 new_data_name,
2871 compress_type=zipfile.ZIP_STORED)
2872 else:
2873 new_data_name = '{}.new.dat'.format(self.partition)
2874 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2875
Dan Albert8e0178d2015-01-27 15:53:15 -08002876 ZipWrite(output_zip,
2877 '{}.patch.dat'.format(self.path),
2878 '{}.patch.dat'.format(self.partition),
2879 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002880
Tianjie Xu209db462016-05-24 17:34:52 -07002881 if self.partition == "system":
2882 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2883 else:
2884 code = ErrorCode.VENDOR_UPDATE_FAILURE
2885
Yifan Hong10c530d2018-12-27 17:34:18 -08002886 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002887 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002888 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002889 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002890 device=self.device, partition=self.partition,
2891 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002892 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002893
Dan Albert8b72aef2015-03-23 19:13:21 -07002894 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002895 data = source.ReadRangeSet(ranges)
2896 ctx = sha1()
2897
2898 for p in data:
2899 ctx.update(p)
2900
2901 return ctx.hexdigest()
2902
Tao Baoe9b61912015-07-09 17:37:49 -07002903 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2904 """Return the hash value for all zero blocks."""
2905 zero_block = '\x00' * 4096
2906 ctx = sha1()
2907 for _ in range(num_blocks):
2908 ctx.update(zero_block)
2909
2910 return ctx.hexdigest()
2911
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002912
Tianjie Xu41976c72019-07-03 13:57:01 -07002913# Expose these two classes to support vendor-specific scripts
2914DataImage = images.DataImage
2915EmptyImage = images.EmptyImage
2916
Tao Bao76def242017-11-21 09:25:31 -08002917
Doug Zongker96a57e72010-09-26 14:57:41 -07002918# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002919PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002920 "ext4": "EMMC",
2921 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002922 "f2fs": "EMMC",
2923 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002924}
Doug Zongker96a57e72010-09-26 14:57:41 -07002925
Yifan Hongbdb32012020-05-07 12:38:53 -07002926def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2927 """
2928 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2929 backwards compatibility. It aborts if the fstab entry has slotselect option
2930 (unless check_no_slot is explicitly set to False).
2931 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002932 fstab = info["fstab"]
2933 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002934 if check_no_slot:
2935 assert not fstab[mount_point].slotselect, \
2936 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002937 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2938 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002939 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002940 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002941
2942
Yifan Hongbdb32012020-05-07 12:38:53 -07002943def GetTypeAndDeviceExpr(mount_point, info):
2944 """
2945 Return the filesystem of the partition, and an edify expression that evaluates
2946 to the device at runtime.
2947 """
2948 fstab = info["fstab"]
2949 if fstab:
2950 p = fstab[mount_point]
2951 device_expr = '"%s"' % fstab[mount_point].device
2952 if p.slotselect:
2953 device_expr = 'add_slot_suffix(%s)' % device_expr
2954 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2955 else:
2956 raise KeyError
2957
2958
2959def GetEntryForDevice(fstab, device):
2960 """
2961 Returns:
2962 The first entry in fstab whose device is the given value.
2963 """
2964 if not fstab:
2965 return None
2966 for mount_point in fstab:
2967 if fstab[mount_point].device == device:
2968 return fstab[mount_point]
2969 return None
2970
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002971def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002972 """Parses and converts a PEM-encoded certificate into DER-encoded.
2973
2974 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2975
2976 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002977 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002978 """
2979 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002980 save = False
2981 for line in data.split("\n"):
2982 if "--END CERTIFICATE--" in line:
2983 break
2984 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002985 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002986 if "--BEGIN CERTIFICATE--" in line:
2987 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002988 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002989 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002990
Tao Bao04e1f012018-02-04 12:13:35 -08002991
2992def ExtractPublicKey(cert):
2993 """Extracts the public key (PEM-encoded) from the given certificate file.
2994
2995 Args:
2996 cert: The certificate filename.
2997
2998 Returns:
2999 The public key string.
3000
3001 Raises:
3002 AssertionError: On non-zero return from 'openssl'.
3003 """
3004 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3005 # While openssl 1.1 writes the key into the given filename followed by '-out',
3006 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3007 # stdout instead.
3008 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3009 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3010 pubkey, stderrdata = proc.communicate()
3011 assert proc.returncode == 0, \
3012 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3013 return pubkey
3014
3015
Tao Bao1ac886e2019-06-26 11:58:22 -07003016def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003017 """Extracts the AVB public key from the given public or private key.
3018
3019 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003020 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003021 key: The input key file, which should be PEM-encoded public or private key.
3022
3023 Returns:
3024 The path to the extracted AVB public key file.
3025 """
3026 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3027 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003028 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003029 return output
3030
3031
Doug Zongker412c02f2014-02-13 10:58:24 -08003032def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3033 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003034 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003035
Tao Bao6d5d6232018-03-09 17:04:42 -08003036 Most of the space in the boot and recovery images is just the kernel, which is
3037 identical for the two, so the resulting patch should be efficient. Add it to
3038 the output zip, along with a shell script that is run from init.rc on first
3039 boot to actually do the patching and install the new recovery image.
3040
3041 Args:
3042 input_dir: The top-level input directory of the target-files.zip.
3043 output_sink: The callback function that writes the result.
3044 recovery_img: File object for the recovery image.
3045 boot_img: File objects for the boot image.
3046 info_dict: A dict returned by common.LoadInfoDict() on the input
3047 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003048 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003049 if info_dict is None:
3050 info_dict = OPTIONS.info_dict
3051
Tao Bao6d5d6232018-03-09 17:04:42 -08003052 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003053 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3054
3055 if board_uses_vendorimage:
3056 # In this case, the output sink is rooted at VENDOR
3057 recovery_img_path = "etc/recovery.img"
3058 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3059 sh_dir = "bin"
3060 else:
3061 # In this case the output sink is rooted at SYSTEM
3062 recovery_img_path = "vendor/etc/recovery.img"
3063 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3064 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003065
Tao Baof2cffbd2015-07-22 12:33:18 -07003066 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003067 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003068
3069 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003070 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003071 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003072 # With system-root-image, boot and recovery images will have mismatching
3073 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3074 # to handle such a case.
3075 if system_root_image:
3076 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003077 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003078 assert not os.path.exists(path)
3079 else:
3080 diff_program = ["imgdiff"]
3081 if os.path.exists(path):
3082 diff_program.append("-b")
3083 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003084 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003085 else:
3086 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003087
3088 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3089 _, _, patch = d.ComputePatch()
3090 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003091
Dan Albertebb19aa2015-03-27 19:11:53 -07003092 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003093 # The following GetTypeAndDevice()s need to use the path in the target
3094 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003095 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3096 check_no_slot=False)
3097 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3098 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003099 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003100 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003101
Tao Baof2cffbd2015-07-22 12:33:18 -07003102 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003103
3104 # Note that we use /vendor to refer to the recovery resources. This will
3105 # work for a separate vendor partition mounted at /vendor or a
3106 # /system/vendor subdirectory on the system partition, for which init will
3107 # create a symlink from /vendor to /system/vendor.
3108
3109 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003110if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3111 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003112 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003113 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3114 log -t recovery "Installing new recovery image: succeeded" || \\
3115 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003116else
3117 log -t recovery "Recovery image already installed"
3118fi
3119""" % {'type': recovery_type,
3120 'device': recovery_device,
3121 'sha1': recovery_img.sha1,
3122 'size': recovery_img.size}
3123 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003124 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003125if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3126 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003127 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003128 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3129 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3130 log -t recovery "Installing new recovery image: succeeded" || \\
3131 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003132else
3133 log -t recovery "Recovery image already installed"
3134fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003135""" % {'boot_size': boot_img.size,
3136 'boot_sha1': boot_img.sha1,
3137 'recovery_size': recovery_img.size,
3138 'recovery_sha1': recovery_img.sha1,
3139 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003140 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3141 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003142 'recovery_device': recovery_device,
3143 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003144
Bill Peckhame868aec2019-09-17 17:06:47 -07003145 # The install script location moved from /system/etc to /system/bin in the L
3146 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3147 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003148
Tao Bao32fcdab2018-10-12 10:30:39 -07003149 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003150
Tao Baoda30cfa2017-12-01 16:19:46 -08003151 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003152
3153
3154class DynamicPartitionUpdate(object):
3155 def __init__(self, src_group=None, tgt_group=None, progress=None,
3156 block_difference=None):
3157 self.src_group = src_group
3158 self.tgt_group = tgt_group
3159 self.progress = progress
3160 self.block_difference = block_difference
3161
3162 @property
3163 def src_size(self):
3164 if not self.block_difference:
3165 return 0
3166 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3167
3168 @property
3169 def tgt_size(self):
3170 if not self.block_difference:
3171 return 0
3172 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3173
3174 @staticmethod
3175 def _GetSparseImageSize(img):
3176 if not img:
3177 return 0
3178 return img.blocksize * img.total_blocks
3179
3180
3181class DynamicGroupUpdate(object):
3182 def __init__(self, src_size=None, tgt_size=None):
3183 # None: group does not exist. 0: no size limits.
3184 self.src_size = src_size
3185 self.tgt_size = tgt_size
3186
3187
3188class DynamicPartitionsDifference(object):
3189 def __init__(self, info_dict, block_diffs, progress_dict=None,
3190 source_info_dict=None):
3191 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003192 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003193
3194 self._remove_all_before_apply = False
3195 if source_info_dict is None:
3196 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003197 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003198
Tao Baof1113e92019-06-18 12:10:14 -07003199 block_diff_dict = collections.OrderedDict(
3200 [(e.partition, e) for e in block_diffs])
3201
Yifan Hong10c530d2018-12-27 17:34:18 -08003202 assert len(block_diff_dict) == len(block_diffs), \
3203 "Duplicated BlockDifference object for {}".format(
3204 [partition for partition, count in
3205 collections.Counter(e.partition for e in block_diffs).items()
3206 if count > 1])
3207
Yifan Hong79997e52019-01-23 16:56:19 -08003208 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003209
3210 for p, block_diff in block_diff_dict.items():
3211 self._partition_updates[p] = DynamicPartitionUpdate()
3212 self._partition_updates[p].block_difference = block_diff
3213
3214 for p, progress in progress_dict.items():
3215 if p in self._partition_updates:
3216 self._partition_updates[p].progress = progress
3217
3218 tgt_groups = shlex.split(info_dict.get(
3219 "super_partition_groups", "").strip())
3220 src_groups = shlex.split(source_info_dict.get(
3221 "super_partition_groups", "").strip())
3222
3223 for g in tgt_groups:
3224 for p in shlex.split(info_dict.get(
3225 "super_%s_partition_list" % g, "").strip()):
3226 assert p in self._partition_updates, \
3227 "{} is in target super_{}_partition_list but no BlockDifference " \
3228 "object is provided.".format(p, g)
3229 self._partition_updates[p].tgt_group = g
3230
3231 for g in src_groups:
3232 for p in shlex.split(source_info_dict.get(
3233 "super_%s_partition_list" % g, "").strip()):
3234 assert p in self._partition_updates, \
3235 "{} is in source super_{}_partition_list but no BlockDifference " \
3236 "object is provided.".format(p, g)
3237 self._partition_updates[p].src_group = g
3238
Yifan Hong45433e42019-01-18 13:55:25 -08003239 target_dynamic_partitions = set(shlex.split(info_dict.get(
3240 "dynamic_partition_list", "").strip()))
3241 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3242 if u.tgt_size)
3243 assert block_diffs_with_target == target_dynamic_partitions, \
3244 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3245 list(target_dynamic_partitions), list(block_diffs_with_target))
3246
3247 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3248 "dynamic_partition_list", "").strip()))
3249 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3250 if u.src_size)
3251 assert block_diffs_with_source == source_dynamic_partitions, \
3252 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3253 list(source_dynamic_partitions), list(block_diffs_with_source))
3254
Yifan Hong10c530d2018-12-27 17:34:18 -08003255 if self._partition_updates:
3256 logger.info("Updating dynamic partitions %s",
3257 self._partition_updates.keys())
3258
Yifan Hong79997e52019-01-23 16:56:19 -08003259 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003260
3261 for g in tgt_groups:
3262 self._group_updates[g] = DynamicGroupUpdate()
3263 self._group_updates[g].tgt_size = int(info_dict.get(
3264 "super_%s_group_size" % g, "0").strip())
3265
3266 for g in src_groups:
3267 if g not in self._group_updates:
3268 self._group_updates[g] = DynamicGroupUpdate()
3269 self._group_updates[g].src_size = int(source_info_dict.get(
3270 "super_%s_group_size" % g, "0").strip())
3271
3272 self._Compute()
3273
3274 def WriteScript(self, script, output_zip, write_verify_script=False):
3275 script.Comment('--- Start patching dynamic partitions ---')
3276 for p, u in self._partition_updates.items():
3277 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3278 script.Comment('Patch partition %s' % p)
3279 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3280 write_verify_script=False)
3281
3282 op_list_path = MakeTempFile()
3283 with open(op_list_path, 'w') as f:
3284 for line in self._op_list:
3285 f.write('{}\n'.format(line))
3286
3287 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3288
3289 script.Comment('Update dynamic partition metadata')
3290 script.AppendExtra('assert(update_dynamic_partitions('
3291 'package_extract_file("dynamic_partitions_op_list")));')
3292
3293 if write_verify_script:
3294 for p, u in self._partition_updates.items():
3295 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3296 u.block_difference.WritePostInstallVerifyScript(script)
3297 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3298
3299 for p, u in self._partition_updates.items():
3300 if u.tgt_size and u.src_size <= u.tgt_size:
3301 script.Comment('Patch partition %s' % p)
3302 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3303 write_verify_script=write_verify_script)
3304 if write_verify_script:
3305 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3306
3307 script.Comment('--- End patching dynamic partitions ---')
3308
3309 def _Compute(self):
3310 self._op_list = list()
3311
3312 def append(line):
3313 self._op_list.append(line)
3314
3315 def comment(line):
3316 self._op_list.append("# %s" % line)
3317
3318 if self._remove_all_before_apply:
3319 comment('Remove all existing dynamic partitions and groups before '
3320 'applying full OTA')
3321 append('remove_all_groups')
3322
3323 for p, u in self._partition_updates.items():
3324 if u.src_group and not u.tgt_group:
3325 append('remove %s' % p)
3326
3327 for p, u in self._partition_updates.items():
3328 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3329 comment('Move partition %s from %s to default' % (p, u.src_group))
3330 append('move %s default' % p)
3331
3332 for p, u in self._partition_updates.items():
3333 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3334 comment('Shrink partition %s from %d to %d' %
3335 (p, u.src_size, u.tgt_size))
3336 append('resize %s %s' % (p, u.tgt_size))
3337
3338 for g, u in self._group_updates.items():
3339 if u.src_size is not None and u.tgt_size is None:
3340 append('remove_group %s' % g)
3341 if (u.src_size is not None and u.tgt_size is not None and
3342 u.src_size > u.tgt_size):
3343 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3344 append('resize_group %s %d' % (g, u.tgt_size))
3345
3346 for g, u in self._group_updates.items():
3347 if u.src_size is None and u.tgt_size is not None:
3348 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3349 append('add_group %s %d' % (g, u.tgt_size))
3350 if (u.src_size is not None and u.tgt_size is not None and
3351 u.src_size < u.tgt_size):
3352 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3353 append('resize_group %s %d' % (g, u.tgt_size))
3354
3355 for p, u in self._partition_updates.items():
3356 if u.tgt_group and not u.src_group:
3357 comment('Add partition %s to group %s' % (p, u.tgt_group))
3358 append('add %s %s' % (p, u.tgt_group))
3359
3360 for p, u in self._partition_updates.items():
3361 if u.tgt_size and u.src_size < u.tgt_size:
3362 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3363 append('resize %s %d' % (p, u.tgt_size))
3364
3365 for p, u in self._partition_updates.items():
3366 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3367 comment('Move partition %s from default to %s' %
3368 (p, u.tgt_group))
3369 append('move %s %s' % (p, u.tgt_group))