blob: f025bb6298637b7b72ddc599db7f143c9fe64413 [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
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Kelvin Zhangc184fa12021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080071 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.extra_signapk_args = []
73 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080074 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080075 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070076 self.public_key_suffix = ".x509.pem"
77 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070078 # use otatools built boot_signer by default
79 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070080 self.boot_signer_args = []
81 self.verity_signer_path = None
82 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070083 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080084 self.aftl_server = None
85 self.aftl_key_path = None
86 self.aftl_manufacturer_key_path = None
87 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070088 self.verbose = False
89 self.tempfiles = []
90 self.device_specific = None
91 self.extras = {}
92 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070093 self.source_info_dict = None
94 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070095 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070096 # Stash size cannot exceed cache_size * threshold.
97 self.cache_size = None
98 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070099 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -0700100 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700101
102
103OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700104
Tao Bao71197512018-10-11 14:08:45 -0700105# The block size that's used across the releasetools scripts.
106BLOCK_SIZE = 4096
107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800108# Values for "certificate" in apkcerts that mean special things.
109SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
110
Tao Bao5cc0abb2019-03-21 10:18:05 -0700111# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
112# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800113# descriptor into vbmeta.img. When adding a new entry here, the
114# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
115# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000116AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
117 'system', 'system_ext', 'vendor', 'vendor_boot',
118 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800119
Tao Bao08c190f2019-06-03 23:07:58 -0700120# Chained VBMeta partitions.
121AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
122
Tianjie Xu861f4132018-09-12 11:49:33 -0700123# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400124PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700125 'system',
126 'vendor',
127 'product',
128 'system_ext',
129 'odm',
130 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700131 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Tao Bao32fcdab2018-10-12 10:30:39 -0700279 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400280 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700281 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700282 return subprocess.Popen(args, **kwargs)
283
284
Tao Bao986ee862018-10-04 15:46:16 -0700285def RunAndCheckOutput(args, verbose=None, **kwargs):
286 """Runs the given command and returns the output.
287
288 Args:
289 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700290 verbose: Whether the commands should be shown. Default to the global
291 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700292 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
293 stdin, etc. stdout and stderr will default to subprocess.PIPE and
294 subprocess.STDOUT respectively unless caller specifies any of them.
295
296 Returns:
297 The output string.
298
299 Raises:
300 ExternalError: On non-zero exit from the command.
301 """
Tao Bao986ee862018-10-04 15:46:16 -0700302 proc = Run(args, verbose=verbose, **kwargs)
303 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800304 if output is None:
305 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700306 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400307 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700308 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700309 if proc.returncode != 0:
310 raise ExternalError(
311 "Failed to run command '{}' (exit code {}):\n{}".format(
312 args, proc.returncode, output))
313 return output
314
315
Tao Baoc765cca2018-01-31 17:32:40 -0800316def RoundUpTo4K(value):
317 rounded_up = value + 4095
318 return rounded_up - (rounded_up % 4096)
319
320
Ying Wang7e6d4e42010-12-13 16:25:36 -0800321def CloseInheritedPipes():
322 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
323 before doing other work."""
324 if platform.system() != "Darwin":
325 return
326 for d in range(3, 1025):
327 try:
328 stat = os.fstat(d)
329 if stat is not None:
330 pipebit = stat[0] & 0x1000
331 if pipebit != 0:
332 os.close(d)
333 except OSError:
334 pass
335
336
Tao Bao1c320f82019-10-04 23:25:12 -0700337class BuildInfo(object):
338 """A class that holds the information for a given build.
339
340 This class wraps up the property querying for a given source or target build.
341 It abstracts away the logic of handling OEM-specific properties, and caches
342 the commonly used properties such as fingerprint.
343
344 There are two types of info dicts: a) build-time info dict, which is generated
345 at build time (i.e. included in a target_files zip); b) OEM info dict that is
346 specified at package generation time (via command line argument
347 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
348 having "oem_fingerprint_properties" in build-time info dict), all the queries
349 would be answered based on build-time info dict only. Otherwise if using
350 OEM-specific properties, some of them will be calculated from two info dicts.
351
352 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800353 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700354
355 Attributes:
356 info_dict: The build-time info dict.
357 is_ab: Whether it's a build that uses A/B OTA.
358 oem_dicts: A list of OEM dicts.
359 oem_props: A list of OEM properties that should be read from OEM dicts; None
360 if the build doesn't use any OEM-specific property.
361 fingerprint: The fingerprint of the build, which would be calculated based
362 on OEM properties if applicable.
363 device: The device name, which could come from OEM dicts if applicable.
364 """
365
366 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
367 "ro.product.manufacturer", "ro.product.model",
368 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
370 "product", "odm", "vendor", "system_ext", "system"]
371 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
372 "product", "product_services", "odm", "vendor", "system"]
373 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700374
Tao Bao3ed35d32019-10-07 20:48:48 -0700375 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700376 """Initializes a BuildInfo instance with the given dicts.
377
378 Note that it only wraps up the given dicts, without making copies.
379
380 Arguments:
381 info_dict: The build-time info dict.
382 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
383 that it always uses the first dict to calculate the fingerprint or the
384 device name. The rest would be used for asserting OEM properties only
385 (e.g. one package can be installed on one of these devices).
386
387 Raises:
388 ValueError: On invalid inputs.
389 """
390 self.info_dict = info_dict
391 self.oem_dicts = oem_dicts
392
393 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700394
Hongguang Chend7c160f2020-05-03 21:24:26 -0700395 # Skip _oem_props if oem_dicts is None to use BuildInfo in
396 # sign_target_files_apks
397 if self.oem_dicts:
398 self._oem_props = info_dict.get("oem_fingerprint_properties")
399 else:
400 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 def check_fingerprint(fingerprint):
403 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
404 raise ValueError(
405 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
406 "3.2.2. Build Parameters.".format(fingerprint))
407
Daniel Normand5fe8622020-01-08 17:01:11 -0800408 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800409 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800410 try:
411 fingerprint = self.CalculatePartitionFingerprint(partition)
412 check_fingerprint(fingerprint)
413 self._partition_fingerprints[partition] = fingerprint
414 except ExternalError:
415 continue
416 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800417 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 # need a fingerprint when creating the image.
419 self._partition_fingerprints[
420 "system_other"] = self._partition_fingerprints["system"]
421
Tao Bao1c320f82019-10-04 23:25:12 -0700422 # These two should be computed only after setting self._oem_props.
423 self._device = self.GetOemProperty("ro.product.device")
424 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800425 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700426
427 @property
428 def is_ab(self):
429 return self._is_ab
430
431 @property
432 def device(self):
433 return self._device
434
435 @property
436 def fingerprint(self):
437 return self._fingerprint
438
439 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700440 def oem_props(self):
441 return self._oem_props
442
443 def __getitem__(self, key):
444 return self.info_dict[key]
445
446 def __setitem__(self, key, value):
447 self.info_dict[key] = value
448
449 def get(self, key, default=None):
450 return self.info_dict.get(key, default)
451
452 def items(self):
453 return self.info_dict.items()
454
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000455 def _GetRawBuildProp(self, prop, partition):
456 prop_file = '{}.build.prop'.format(
457 partition) if partition else 'build.prop'
458 partition_props = self.info_dict.get(prop_file)
459 if not partition_props:
460 return None
461 return partition_props.GetProp(prop)
462
Daniel Normand5fe8622020-01-08 17:01:11 -0800463 def GetPartitionBuildProp(self, prop, partition):
464 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800465
466 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400467 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800468
Daniel Normand5fe8622020-01-08 17:01:11 -0800469 # If provided a partition for this property, only look within that
470 # partition's build.prop.
471 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800472 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800473 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800474 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000475
476 prop_val = self._GetRawBuildProp(prop, partition)
477 if prop_val is not None:
478 return prop_val
479 raise ExternalError("couldn't find %s in %s.build.prop" %
480 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800481
Tao Bao1c320f82019-10-04 23:25:12 -0700482 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800483 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700484 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
485 return self._ResolveRoProductBuildProp(prop)
486
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000487 prop_val = self._GetRawBuildProp(prop, None)
488 if prop_val is not None:
489 return prop_val
490
491 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700492
493 def _ResolveRoProductBuildProp(self, prop):
494 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000495 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700496 if prop_val:
497 return prop_val
498
Steven Laver8e2086e2020-04-27 16:26:31 -0700499 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000500 source_order_val = self._GetRawBuildProp(
501 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700502 if source_order_val:
503 source_order = source_order_val.split(",")
504 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700505 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700506
507 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700508 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700509 raise ExternalError(
510 "Invalid ro.product.property_source_order '{}'".format(source_order))
511
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700513 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514 "ro.product", "ro.product.{}".format(source_partition), 1)
515 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700516 if prop_val:
517 return prop_val
518
519 raise ExternalError("couldn't resolve {}".format(prop))
520
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 def _GetRoProductPropsDefaultSourceOrder(self):
522 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
523 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000524 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700525 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000526 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700527 if android_version == "10":
528 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
529 # NOTE: float() conversion of android_version will have rounding error.
530 # We are checking for "9" or less, and using "< 10" is well outside of
531 # possible floating point rounding.
532 try:
533 android_version_val = float(android_version)
534 except ValueError:
535 android_version_val = 0
536 if android_version_val < 10:
537 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
538 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
539
Tianjieb37c5be2020-10-15 21:27:10 -0700540 def _GetPlatformVersion(self):
541 version_sdk = self.GetBuildProp("ro.build.version.sdk")
542 # init code switches to version_release_or_codename (see b/158483506). After
543 # API finalization, release_or_codename will be the same as release. This
544 # is the best effort to support pre-S dev stage builds.
545 if int(version_sdk) >= 30:
546 try:
547 return self.GetBuildProp("ro.build.version.release_or_codename")
548 except ExternalError:
549 logger.warning('Failed to find ro.build.version.release_or_codename')
550
551 return self.GetBuildProp("ro.build.version.release")
552
553 def _GetPartitionPlatformVersion(self, partition):
554 try:
555 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
556 partition)
557 except ExternalError:
558 return self.GetPartitionBuildProp("ro.build.version.release",
559 partition)
560
Tao Bao1c320f82019-10-04 23:25:12 -0700561 def GetOemProperty(self, key):
562 if self.oem_props is not None and key in self.oem_props:
563 return self.oem_dicts[0][key]
564 return self.GetBuildProp(key)
565
Daniel Normand5fe8622020-01-08 17:01:11 -0800566 def GetPartitionFingerprint(self, partition):
567 return self._partition_fingerprints.get(partition, None)
568
569 def CalculatePartitionFingerprint(self, partition):
570 try:
571 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
572 except ExternalError:
573 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
574 self.GetPartitionBuildProp("ro.product.brand", partition),
575 self.GetPartitionBuildProp("ro.product.name", partition),
576 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700577 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800578 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400579 self.GetPartitionBuildProp(
580 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800581 self.GetPartitionBuildProp("ro.build.type", partition),
582 self.GetPartitionBuildProp("ro.build.tags", partition))
583
Tao Bao1c320f82019-10-04 23:25:12 -0700584 def CalculateFingerprint(self):
585 if self.oem_props is None:
586 try:
587 return self.GetBuildProp("ro.build.fingerprint")
588 except ExternalError:
589 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
590 self.GetBuildProp("ro.product.brand"),
591 self.GetBuildProp("ro.product.name"),
592 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700593 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700594 self.GetBuildProp("ro.build.id"),
595 self.GetBuildProp("ro.build.version.incremental"),
596 self.GetBuildProp("ro.build.type"),
597 self.GetBuildProp("ro.build.tags"))
598 return "%s/%s/%s:%s" % (
599 self.GetOemProperty("ro.product.brand"),
600 self.GetOemProperty("ro.product.name"),
601 self.GetOemProperty("ro.product.device"),
602 self.GetBuildProp("ro.build.thumbprint"))
603
604 def WriteMountOemScript(self, script):
605 assert self.oem_props is not None
606 recovery_mount_options = self.info_dict.get("recovery_mount_options")
607 script.Mount("/oem", recovery_mount_options)
608
609 def WriteDeviceAssertions(self, script, oem_no_mount):
610 # Read the property directly if not using OEM properties.
611 if not self.oem_props:
612 script.AssertDevice(self.device)
613 return
614
615 # Otherwise assert OEM properties.
616 if not self.oem_dicts:
617 raise ExternalError(
618 "No OEM file provided to answer expected assertions")
619
620 for prop in self.oem_props.split():
621 values = []
622 for oem_dict in self.oem_dicts:
623 if prop in oem_dict:
624 values.append(oem_dict[prop])
625 if not values:
626 raise ExternalError(
627 "The OEM file is missing the property %s" % (prop,))
628 script.AssertOemProperty(prop, values, oem_no_mount)
629
630
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000631def ReadFromInputFile(input_file, fn):
632 """Reads the contents of fn from input zipfile or directory."""
633 if isinstance(input_file, zipfile.ZipFile):
634 return input_file.read(fn).decode()
635 else:
636 path = os.path.join(input_file, *fn.split("/"))
637 try:
638 with open(path) as f:
639 return f.read()
640 except IOError as e:
641 if e.errno == errno.ENOENT:
642 raise KeyError(fn)
643
644
Yifan Hong10482a22021-01-07 14:38:41 -0800645def ExtractFromInputFile(input_file, fn):
646 """Extracts the contents of fn from input zipfile or directory into a file."""
647 if isinstance(input_file, zipfile.ZipFile):
648 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500649 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800650 f.write(input_file.read(fn))
651 return tmp_file
652 else:
653 file = os.path.join(input_file, *fn.split("/"))
654 if not os.path.exists(file):
655 raise KeyError(fn)
656 return file
657
jiajia tangf3f842b2021-03-17 21:49:44 +0800658class RamdiskFormat(object):
659 LZ4 = 1
660 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800661
Tao Bao410ad8b2018-08-24 12:08:38 -0700662def LoadInfoDict(input_file, repacking=False):
663 """Loads the key/value pairs from the given input target_files.
664
Tianjiea85bdf02020-07-29 11:56:19 -0700665 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700666 checks and returns the parsed key/value pairs for to the given build. It's
667 usually called early when working on input target_files files, e.g. when
668 generating OTAs, or signing builds. Note that the function may be called
669 against an old target_files file (i.e. from past dessert releases). So the
670 property parsing needs to be backward compatible.
671
672 In a `META/misc_info.txt`, a few properties are stored as links to the files
673 in the PRODUCT_OUT directory. It works fine with the build system. However,
674 they are no longer available when (re)generating images from target_files zip.
675 When `repacking` is True, redirect these properties to the actual files in the
676 unzipped directory.
677
678 Args:
679 input_file: The input target_files file, which could be an open
680 zipfile.ZipFile instance, or a str for the dir that contains the files
681 unzipped from a target_files file.
682 repacking: Whether it's trying repack an target_files file after loading the
683 info dict (default: False). If so, it will rewrite a few loaded
684 properties (e.g. selinux_fc, root_dir) to point to the actual files in
685 target_files file. When doing repacking, `input_file` must be a dir.
686
687 Returns:
688 A dict that contains the parsed key/value pairs.
689
690 Raises:
691 AssertionError: On invalid input arguments.
692 ValueError: On malformed input values.
693 """
694 if repacking:
695 assert isinstance(input_file, str), \
696 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700697
Doug Zongkerc9253822014-02-04 12:17:58 -0800698 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000699 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800700
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700701 try:
Michael Runge6e836112014-04-15 17:40:21 -0700702 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700703 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700704 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700705
Tao Bao410ad8b2018-08-24 12:08:38 -0700706 if "recovery_api_version" not in d:
707 raise ValueError("Failed to find 'recovery_api_version'")
708 if "fstab_version" not in d:
709 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800710
Tao Bao410ad8b2018-08-24 12:08:38 -0700711 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700712 # "selinux_fc" properties should point to the file_contexts files
713 # (file_contexts.bin) under META/.
714 for key in d:
715 if key.endswith("selinux_fc"):
716 fc_basename = os.path.basename(d[key])
717 fc_config = os.path.join(input_file, "META", fc_basename)
718 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700719
Daniel Norman72c626f2019-05-13 15:58:14 -0700720 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700721
Tom Cherryd14b8952018-08-09 14:26:00 -0700722 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700723 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700724 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700725 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700726
David Anderson0ec64ac2019-12-06 12:21:18 -0800727 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700728 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700729 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800730 key_name = part_name + "_base_fs_file"
731 if key_name not in d:
732 continue
733 basename = os.path.basename(d[key_name])
734 base_fs_file = os.path.join(input_file, "META", basename)
735 if os.path.exists(base_fs_file):
736 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700737 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700738 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800739 "Failed to find %s base fs file: %s", part_name, base_fs_file)
740 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700741
Doug Zongker37974732010-09-16 17:44:38 -0700742 def makeint(key):
743 if key in d:
744 d[key] = int(d[key], 0)
745
746 makeint("recovery_api_version")
747 makeint("blocksize")
748 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700749 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700750 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700751 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700752 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800753 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700754
Steve Muckle903a1ca2020-05-07 17:32:10 -0700755 boot_images = "boot.img"
756 if "boot_images" in d:
757 boot_images = d["boot_images"]
758 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400759 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700760
Tao Bao765668f2019-10-04 22:03:00 -0700761 # Load recovery fstab if applicable.
762 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tangf3f842b2021-03-17 21:49:44 +0800763 if d.get('lz4_ramdisks') == 'true':
764 ramdisk_format = RamdiskFormat.LZ4
765 else:
766 ramdisk_format = RamdiskFormat.GZ
Tianjie Xucfa86222016-03-07 16:31:19 -0800767
Tianjie Xu861f4132018-09-12 11:49:33 -0700768 # Tries to load the build props for all partitions with care_map, including
769 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800770 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800771 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000772 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800773 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700774 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800775
Tao Bao3ed35d32019-10-07 20:48:48 -0700776 # Set up the salt (based on fingerprint) that will be used when adding AVB
777 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800778 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700779 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800780 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800781 fingerprint = build_info.GetPartitionFingerprint(partition)
782 if fingerprint:
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400783 d["avb_{}_salt".format(partition)] = sha256(
784 fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400785 try:
786 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
787 except KeyError:
788 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700789 return d
790
Tao Baod1de6f32017-03-01 16:38:48 -0800791
Daniel Norman4cc9df62019-07-18 10:11:07 -0700792def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900793 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700794 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900795
Daniel Norman4cc9df62019-07-18 10:11:07 -0700796
797def LoadDictionaryFromFile(file_path):
798 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900799 return LoadDictionaryFromLines(lines)
800
801
Michael Runge6e836112014-04-15 17:40:21 -0700802def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700803 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700804 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700805 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700806 if not line or line.startswith("#"):
807 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700808 if "=" in line:
809 name, value = line.split("=", 1)
810 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700811 return d
812
Tao Baod1de6f32017-03-01 16:38:48 -0800813
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000814class PartitionBuildProps(object):
815 """The class holds the build prop of a particular partition.
816
817 This class loads the build.prop and holds the build properties for a given
818 partition. It also partially recognizes the 'import' statement in the
819 build.prop; and calculates alternative values of some specific build
820 properties during runtime.
821
822 Attributes:
823 input_file: a zipped target-file or an unzipped target-file directory.
824 partition: name of the partition.
825 props_allow_override: a list of build properties to search for the
826 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 build_props: a dict of build properties for the given partition.
828 prop_overrides: a set of props that are overridden by import.
829 placeholder_values: A dict of runtime variables' values to replace the
830 placeholders in the build.prop file. We expect exactly one value for
831 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800832 ramdisk_format: If name is "boot", the format of ramdisk inside the
833 boot image. Otherwise, its value is ignored.
834 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000835 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400836
Tianjie Xu9afb2212020-05-10 21:48:15 +0000837 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000838 self.input_file = input_file
839 self.partition = name
840 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000843 self.prop_overrides = set()
844 self.placeholder_values = {}
845 if placeholder_values:
846 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000847
848 @staticmethod
849 def FromDictionary(name, build_props):
850 """Constructs an instance from a build prop dictionary."""
851
852 props = PartitionBuildProps("unknown", name)
853 props.build_props = build_props.copy()
854 return props
855
856 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800857 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000858 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800859
860 if name == "boot":
jiajia tangf3f842b2021-03-17 21:49:44 +0800861 data = PartitionBuildProps._ReadBootPropFile(input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800862 else:
863 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
864
865 props = PartitionBuildProps(input_file, name, placeholder_values)
866 props._LoadBuildProp(data)
867 return props
868
869 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800870 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800871 """
872 Read build.prop for boot image from input_file.
873 Return empty string if not found.
874 """
875 try:
876 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
877 except KeyError:
878 logger.warning('Failed to read IMAGES/boot.img')
879 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800880 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800881 if prop_file is None:
882 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500883 with open(prop_file, "r") as f:
884 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800885
886 @staticmethod
887 def _ReadPartitionPropFile(input_file, name):
888 """
889 Read build.prop for name from input_file.
890 Return empty string if not found.
891 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000892 data = ''
893 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
894 '{}/build.prop'.format(name.upper())]:
895 try:
896 data = ReadFromInputFile(input_file, prop_file)
897 break
898 except KeyError:
899 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800900 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000901
Yifan Hong125d0b62020-09-24 17:07:03 -0700902 @staticmethod
903 def FromBuildPropFile(name, build_prop_file):
904 """Constructs an instance from a build prop file."""
905
906 props = PartitionBuildProps("unknown", name)
907 with open(build_prop_file) as f:
908 props._LoadBuildProp(f.read())
909 return props
910
Tianjie Xu9afb2212020-05-10 21:48:15 +0000911 def _LoadBuildProp(self, data):
912 for line in data.split('\n'):
913 line = line.strip()
914 if not line or line.startswith("#"):
915 continue
916 if line.startswith("import"):
917 overrides = self._ImportParser(line)
918 duplicates = self.prop_overrides.intersection(overrides.keys())
919 if duplicates:
920 raise ValueError('prop {} is overridden multiple times'.format(
921 ','.join(duplicates)))
922 self.prop_overrides = self.prop_overrides.union(overrides.keys())
923 self.build_props.update(overrides)
924 elif "=" in line:
925 name, value = line.split("=", 1)
926 if name in self.prop_overrides:
927 raise ValueError('prop {} is set again after overridden by import '
928 'statement'.format(name))
929 self.build_props[name] = value
930
931 def _ImportParser(self, line):
932 """Parses the build prop in a given import statement."""
933
934 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400935 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000936 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700937
938 if len(tokens) == 3:
939 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
940 return {}
941
Tianjie Xu9afb2212020-05-10 21:48:15 +0000942 import_path = tokens[1]
943 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
944 raise ValueError('Unrecognized import path {}'.format(line))
945
946 # We only recognize a subset of import statement that the init process
947 # supports. And we can loose the restriction based on how the dynamic
948 # fingerprint is used in practice. The placeholder format should be
949 # ${placeholder}, and its value should be provided by the caller through
950 # the placeholder_values.
951 for prop, value in self.placeholder_values.items():
952 prop_place_holder = '${{{}}}'.format(prop)
953 if prop_place_holder in import_path:
954 import_path = import_path.replace(prop_place_holder, value)
955 if '$' in import_path:
956 logger.info('Unresolved place holder in import path %s', import_path)
957 return {}
958
959 import_path = import_path.replace('/{}'.format(self.partition),
960 self.partition.upper())
961 logger.info('Parsing build props override from %s', import_path)
962
963 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
964 d = LoadDictionaryFromLines(lines)
965 return {key: val for key, val in d.items()
966 if key in self.props_allow_override}
967
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000968 def GetProp(self, prop):
969 return self.build_props.get(prop)
970
971
Tianjie Xucfa86222016-03-07 16:31:19 -0800972def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
973 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700974 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700975 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 self.mount_point = mount_point
977 self.fs_type = fs_type
978 self.device = device
979 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700980 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700981 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700982
983 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800984 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700985 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700986 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700987 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700988
Tao Baod1de6f32017-03-01 16:38:48 -0800989 assert fstab_version == 2
990
991 d = {}
992 for line in data.split("\n"):
993 line = line.strip()
994 if not line or line.startswith("#"):
995 continue
996
997 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
998 pieces = line.split()
999 if len(pieces) != 5:
1000 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1001
1002 # Ignore entries that are managed by vold.
1003 options = pieces[4]
1004 if "voldmanaged=" in options:
1005 continue
1006
1007 # It's a good line, parse it.
1008 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001009 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001010 options = options.split(",")
1011 for i in options:
1012 if i.startswith("length="):
1013 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001014 elif i == "slotselect":
1015 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001016 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001017 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001018 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001019
Tao Baod1de6f32017-03-01 16:38:48 -08001020 mount_flags = pieces[3]
1021 # Honor the SELinux context if present.
1022 context = None
1023 for i in mount_flags.split(","):
1024 if i.startswith("context="):
1025 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001026
Tao Baod1de6f32017-03-01 16:38:48 -08001027 mount_point = pieces[1]
1028 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001029 device=pieces[0], length=length, context=context,
1030 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001031
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001032 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001033 # system. Other areas assume system is always at "/system" so point /system
1034 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001035 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001036 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001037 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001038 return d
1039
1040
Tao Bao765668f2019-10-04 22:03:00 -07001041def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1042 """Finds the path to recovery fstab and loads its contents."""
1043 # recovery fstab is only meaningful when installing an update via recovery
1044 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001045 if info_dict.get('ab_update') == 'true' and \
1046 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001047 return None
1048
1049 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1050 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1051 # cases, since it may load the info_dict from an old build (e.g. when
1052 # generating incremental OTAs from that build).
1053 system_root_image = info_dict.get('system_root_image') == 'true'
1054 if info_dict.get('no_recovery') != 'true':
1055 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1056 if isinstance(input_file, zipfile.ZipFile):
1057 if recovery_fstab_path not in input_file.namelist():
1058 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1059 else:
1060 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1061 if not os.path.exists(path):
1062 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1063 return LoadRecoveryFSTab(
1064 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1065 system_root_image)
1066
1067 if info_dict.get('recovery_as_boot') == 'true':
1068 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1069 if isinstance(input_file, zipfile.ZipFile):
1070 if recovery_fstab_path not in input_file.namelist():
1071 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1072 else:
1073 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1074 if not os.path.exists(path):
1075 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1076 return LoadRecoveryFSTab(
1077 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1078 system_root_image)
1079
1080 return None
1081
1082
Doug Zongker37974732010-09-16 17:44:38 -07001083def DumpInfoDict(d):
1084 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001085 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001086
Dan Albert8b72aef2015-03-23 19:13:21 -07001087
Daniel Norman55417142019-11-25 16:04:36 -08001088def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001089 """Merges dynamic partition info variables.
1090
1091 Args:
1092 framework_dict: The dictionary of dynamic partition info variables from the
1093 partial framework target files.
1094 vendor_dict: The dictionary of dynamic partition info variables from the
1095 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001096
1097 Returns:
1098 The merged dynamic partition info dictionary.
1099 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001100
1101 def uniq_concat(a, b):
1102 combined = set(a.split(" "))
1103 combined.update(set(b.split(" ")))
1104 combined = [item.strip() for item in combined if item.strip()]
1105 return " ".join(sorted(combined))
1106
1107 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001108 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001109 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1110
1111 merged_dict = {"use_dynamic_partitions": "true"}
1112
1113 merged_dict["dynamic_partition_list"] = uniq_concat(
1114 framework_dict.get("dynamic_partition_list", ""),
1115 vendor_dict.get("dynamic_partition_list", ""))
1116
1117 # Super block devices are defined by the vendor dict.
1118 if "super_block_devices" in vendor_dict:
1119 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1120 for block_device in merged_dict["super_block_devices"].split(" "):
1121 key = "super_%s_device_size" % block_device
1122 if key not in vendor_dict:
1123 raise ValueError("Vendor dict does not contain required key %s." % key)
1124 merged_dict[key] = vendor_dict[key]
1125
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001126 # Partition groups and group sizes are defined by the vendor dict because
1127 # these values may vary for each board that uses a shared system image.
1128 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001129 for partition_group in merged_dict["super_partition_groups"].split(" "):
1130 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001131 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001132 if key not in vendor_dict:
1133 raise ValueError("Vendor dict does not contain required key %s." % key)
1134 merged_dict[key] = vendor_dict[key]
1135
1136 # Set the partition group's partition list using a concatenation of the
1137 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001138 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001139 merged_dict[key] = uniq_concat(
1140 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301141
Daniel Normanb0c75912020-09-24 14:30:21 -07001142 # Various other flags should be copied from the vendor dict, if defined.
1143 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1144 "super_metadata_device", "super_partition_error_limit",
1145 "super_partition_size"):
1146 if key in vendor_dict.keys():
1147 merged_dict[key] = vendor_dict[key]
1148
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001149 return merged_dict
1150
1151
Daniel Norman21c34f72020-11-11 17:25:50 -08001152def PartitionMapFromTargetFiles(target_files_dir):
1153 """Builds a map from partition -> path within an extracted target files directory."""
1154 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1155 possible_subdirs = {
1156 "system": ["SYSTEM"],
1157 "vendor": ["VENDOR", "SYSTEM/vendor"],
1158 "product": ["PRODUCT", "SYSTEM/product"],
1159 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1160 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1161 "vendor_dlkm": [
1162 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1163 ],
1164 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1165 }
1166 partition_map = {}
1167 for partition, subdirs in possible_subdirs.items():
1168 for subdir in subdirs:
1169 if os.path.exists(os.path.join(target_files_dir, subdir)):
1170 partition_map[partition] = subdir
1171 break
1172 return partition_map
1173
1174
Daniel Normand3351562020-10-29 12:33:11 -07001175def SharedUidPartitionViolations(uid_dict, partition_groups):
1176 """Checks for APK sharedUserIds that cross partition group boundaries.
1177
1178 This uses a single or merged build's shareduid_violation_modules.json
1179 output file, as generated by find_shareduid_violation.py or
1180 core/tasks/find-shareduid-violation.mk.
1181
1182 An error is defined as a sharedUserId that is found in a set of partitions
1183 that span more than one partition group.
1184
1185 Args:
1186 uid_dict: A dictionary created by using the standard json module to read a
1187 complete shareduid_violation_modules.json file.
1188 partition_groups: A list of groups, where each group is a list of
1189 partitions.
1190
1191 Returns:
1192 A list of error messages.
1193 """
1194 errors = []
1195 for uid, partitions in uid_dict.items():
1196 found_in_groups = [
1197 group for group in partition_groups
1198 if set(partitions.keys()) & set(group)
1199 ]
1200 if len(found_in_groups) > 1:
1201 errors.append(
1202 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1203 % (uid, ",".join(sorted(partitions.keys()))))
1204 return errors
1205
1206
Daniel Norman21c34f72020-11-11 17:25:50 -08001207def RunHostInitVerifier(product_out, partition_map):
1208 """Runs host_init_verifier on the init rc files within partitions.
1209
1210 host_init_verifier searches the etc/init path within each partition.
1211
1212 Args:
1213 product_out: PRODUCT_OUT directory, containing partition directories.
1214 partition_map: A map of partition name -> relative path within product_out.
1215 """
1216 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1217 cmd = ["host_init_verifier"]
1218 for partition, path in partition_map.items():
1219 if partition not in allowed_partitions:
1220 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1221 partition)
1222 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1223 # Add --property-contexts if the file exists on the partition.
1224 property_contexts = "%s_property_contexts" % (
1225 "plat" if partition == "system" else partition)
1226 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1227 property_contexts)
1228 if os.path.exists(property_contexts_path):
1229 cmd.append("--property-contexts=%s" % property_contexts_path)
1230 # Add the passwd file if the file exists on the partition.
1231 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1232 if os.path.exists(passwd_path):
1233 cmd.extend(["-p", passwd_path])
1234 return RunAndCheckOutput(cmd)
1235
1236
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001237def AppendAVBSigningArgs(cmd, partition):
1238 """Append signing arguments for avbtool."""
1239 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1240 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001241 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1242 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1243 if os.path.exists(new_key_path):
1244 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001245 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1246 if key_path and algorithm:
1247 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001248 avb_salt = OPTIONS.info_dict.get("avb_salt")
1249 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001250 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001251 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001252
1253
Tao Bao765668f2019-10-04 22:03:00 -07001254def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001255 """Returns the VBMeta arguments for partition.
1256
1257 It sets up the VBMeta argument by including the partition descriptor from the
1258 given 'image', or by configuring the partition as a chained partition.
1259
1260 Args:
1261 partition: The name of the partition (e.g. "system").
1262 image: The path to the partition image.
1263 info_dict: A dict returned by common.LoadInfoDict(). Will use
1264 OPTIONS.info_dict if None has been given.
1265
1266 Returns:
1267 A list of VBMeta arguments.
1268 """
1269 if info_dict is None:
1270 info_dict = OPTIONS.info_dict
1271
1272 # Check if chain partition is used.
1273 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001274 if not key_path:
1275 return ["--include_descriptors_from_image", image]
1276
1277 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1278 # into vbmeta.img. The recovery image will be configured on an independent
1279 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1280 # See details at
1281 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001282 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001283 return []
1284
1285 # Otherwise chain the partition into vbmeta.
1286 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1287 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001288
1289
Tao Bao02a08592018-07-22 12:40:45 -07001290def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1291 """Constructs and returns the arg to build or verify a chained partition.
1292
1293 Args:
1294 partition: The partition name.
1295 info_dict: The info dict to look up the key info and rollback index
1296 location.
1297 key: The key to be used for building or verifying the partition. Defaults to
1298 the key listed in info_dict.
1299
1300 Returns:
1301 A string of form "partition:rollback_index_location:key" that can be used to
1302 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001303 """
1304 if key is None:
1305 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001306 if key and not os.path.exists(key) and OPTIONS.search_path:
1307 new_key_path = os.path.join(OPTIONS.search_path, key)
1308 if os.path.exists(new_key_path):
1309 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001310 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001311 rollback_index_location = info_dict[
1312 "avb_" + partition + "_rollback_index_location"]
1313 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1314
1315
Tianjie20dd8f22020-04-19 15:51:16 -07001316def ConstructAftlMakeImageCommands(output_image):
1317 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001318
1319 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001320 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001321 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1322 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1323 'No AFTL manufacturer key provided.'
1324
1325 vbmeta_image = MakeTempFile()
1326 os.rename(output_image, vbmeta_image)
1327 build_info = BuildInfo(OPTIONS.info_dict)
1328 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001329 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001330 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001331 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 "--vbmeta_image_path", vbmeta_image,
1333 "--output", output_image,
1334 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001335 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001336 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1337 "--algorithm", "SHA256_RSA4096",
1338 "--padding", "4096"]
1339 if OPTIONS.aftl_signer_helper:
1340 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001341 return aftl_cmd
1342
1343
1344def AddAftlInclusionProof(output_image):
1345 """Appends the aftl inclusion proof to the vbmeta image."""
1346
1347 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001348 RunAndCheckOutput(aftl_cmd)
1349
1350 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1351 output_image, '--transparency_log_pub_keys',
1352 OPTIONS.aftl_key_path]
1353 RunAndCheckOutput(verify_cmd)
1354
1355
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001356def AppendGkiSigningArgs(cmd):
1357 """Append GKI signing arguments for mkbootimg."""
1358 # e.g., --gki_signing_key path/to/signing_key
1359 # --gki_signing_algorithm SHA256_RSA4096"
1360
1361 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1362 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1363 if not key_path:
1364 return
1365
1366 if not os.path.exists(key_path) and OPTIONS.search_path:
1367 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1368 if os.path.exists(new_key_path):
1369 key_path = new_key_path
1370
1371 # Checks key_path exists, before appending --gki_signing_* args.
1372 if not os.path.exists(key_path):
1373 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1374
1375 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1376 if key_path and algorithm:
1377 cmd.extend(["--gki_signing_key", key_path,
1378 "--gki_signing_algorithm", algorithm])
1379
1380 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1381 if signature_args:
1382 cmd.extend(["--gki_signing_signature_args", signature_args])
1383
1384
Daniel Norman276f0622019-07-26 14:13:51 -07001385def BuildVBMeta(image_path, partitions, name, needed_partitions):
1386 """Creates a VBMeta image.
1387
1388 It generates the requested VBMeta image. The requested image could be for
1389 top-level or chained VBMeta image, which is determined based on the name.
1390
1391 Args:
1392 image_path: The output path for the new VBMeta image.
1393 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001394 values. Only valid partition names are accepted, as partitions listed
1395 in common.AVB_PARTITIONS and custom partitions listed in
1396 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001397 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1398 needed_partitions: Partitions whose descriptors should be included into the
1399 generated VBMeta image.
1400
1401 Raises:
1402 AssertionError: On invalid input args.
1403 """
1404 avbtool = OPTIONS.info_dict["avb_avbtool"]
1405 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1406 AppendAVBSigningArgs(cmd, name)
1407
Hongguang Chenf23364d2020-04-27 18:36:36 -07001408 custom_partitions = OPTIONS.info_dict.get(
1409 "avb_custom_images_partition_list", "").strip().split()
1410
Daniel Norman276f0622019-07-26 14:13:51 -07001411 for partition, path in partitions.items():
1412 if partition not in needed_partitions:
1413 continue
1414 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001415 partition in AVB_VBMETA_PARTITIONS or
1416 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001417 'Unknown partition: {}'.format(partition)
1418 assert os.path.exists(path), \
1419 'Failed to find {} for {}'.format(path, partition)
1420 cmd.extend(GetAvbPartitionArg(partition, path))
1421
1422 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1423 if args and args.strip():
1424 split_args = shlex.split(args)
1425 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001426 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001427 # as a path relative to source tree, which may not be available at the
1428 # same location when running this script (we have the input target_files
1429 # zip only). For such cases, we additionally scan other locations (e.g.
1430 # IMAGES/, RADIO/, etc) before bailing out.
1431 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001432 chained_image = split_args[index + 1]
1433 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001434 continue
1435 found = False
1436 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1437 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001438 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001439 if os.path.exists(alt_path):
1440 split_args[index + 1] = alt_path
1441 found = True
1442 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001443 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001444 cmd.extend(split_args)
1445
1446 RunAndCheckOutput(cmd)
1447
Tianjie Xueaed60c2020-03-12 00:33:28 -07001448 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001449 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001450 AddAftlInclusionProof(image_path)
1451
Daniel Norman276f0622019-07-26 14:13:51 -07001452
J. Avila98cd4cc2020-06-10 20:09:10 +00001453def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001454 ramdisk_img = tempfile.NamedTemporaryFile()
1455
1456 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1457 cmd = ["mkbootfs", "-f", fs_config_file,
1458 os.path.join(sourcedir, "RAMDISK")]
1459 else:
1460 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1461 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001462 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001463 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001464 stdout=ramdisk_img.file.fileno())
1465 else:
1466 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001467
1468 p2.wait()
1469 p1.wait()
1470 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001471 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001472
1473 return ramdisk_img
1474
1475
Steve Muckle9793cf62020-04-08 18:27:00 -07001476def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001477 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001478 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001479
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001480 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001481 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1482 we are building a two-step special image (i.e. building a recovery image to
1483 be loaded into /boot in two-step OTAs).
1484
1485 Return the image data, or None if sourcedir does not appear to contains files
1486 for building the requested image.
1487 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001488
Yifan Hong63c5ca12020-10-08 11:54:02 -07001489 if info_dict is None:
1490 info_dict = OPTIONS.info_dict
1491
Steve Muckle9793cf62020-04-08 18:27:00 -07001492 # "boot" or "recovery", without extension.
1493 partition_name = os.path.basename(sourcedir).lower()
1494
Yifan Hong63c5ca12020-10-08 11:54:02 -07001495 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001496 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001497 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1498 logger.info("Excluded kernel binary from recovery image.")
1499 else:
1500 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001501 else:
1502 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001503 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001504 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001505 return None
1506
1507 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001508 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001509
Doug Zongkereef39442009-04-02 12:14:19 -07001510 img = tempfile.NamedTemporaryFile()
1511
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001512 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001513 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1514 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001515
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001516 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1517 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1518
Yifan Hong63c5ca12020-10-08 11:54:02 -07001519 cmd = [mkbootimg]
1520 if kernel:
1521 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001522
Benoit Fradina45a8682014-07-14 21:00:43 +02001523 fn = os.path.join(sourcedir, "second")
1524 if os.access(fn, os.F_OK):
1525 cmd.append("--second")
1526 cmd.append(fn)
1527
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001528 fn = os.path.join(sourcedir, "dtb")
1529 if os.access(fn, os.F_OK):
1530 cmd.append("--dtb")
1531 cmd.append(fn)
1532
Doug Zongker171f1cd2009-06-15 22:36:37 -07001533 fn = os.path.join(sourcedir, "cmdline")
1534 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001535 cmd.append("--cmdline")
1536 cmd.append(open(fn).read().rstrip("\n"))
1537
1538 fn = os.path.join(sourcedir, "base")
1539 if os.access(fn, os.F_OK):
1540 cmd.append("--base")
1541 cmd.append(open(fn).read().rstrip("\n"))
1542
Ying Wang4de6b5b2010-08-25 14:29:34 -07001543 fn = os.path.join(sourcedir, "pagesize")
1544 if os.access(fn, os.F_OK):
1545 cmd.append("--pagesize")
1546 cmd.append(open(fn).read().rstrip("\n"))
1547
Steve Mucklef84668e2020-03-16 19:13:46 -07001548 if partition_name == "recovery":
1549 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301550 if not args:
1551 # Fall back to "mkbootimg_args" for recovery image
1552 # in case "recovery_mkbootimg_args" is not set.
1553 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001554 else:
1555 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001556 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001557 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001558
Tao Bao76def242017-11-21 09:25:31 -08001559 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001560 if args and args.strip():
1561 cmd.extend(shlex.split(args))
1562
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001563 if has_ramdisk:
1564 cmd.extend(["--ramdisk", ramdisk_img.name])
1565
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001566 AppendGkiSigningArgs(cmd)
1567
Tao Baod95e9fd2015-03-29 23:07:41 -07001568 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001569 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001570 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001571 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001572 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001573 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001574
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001575 if partition_name == "recovery":
1576 if info_dict.get("include_recovery_dtbo") == "true":
1577 fn = os.path.join(sourcedir, "recovery_dtbo")
1578 cmd.extend(["--recovery_dtbo", fn])
1579 if info_dict.get("include_recovery_acpio") == "true":
1580 fn = os.path.join(sourcedir, "recovery_acpio")
1581 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001582
Tao Bao986ee862018-10-04 15:46:16 -07001583 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001584
Tao Bao76def242017-11-21 09:25:31 -08001585 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001586 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001587 # Hard-code the path as "/boot" for two-step special recovery image (which
1588 # will be loaded into /boot during the two-step OTA).
1589 if two_step_image:
1590 path = "/boot"
1591 else:
Tao Baobf70c312017-07-11 17:27:55 -07001592 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001593 cmd = [OPTIONS.boot_signer_path]
1594 cmd.extend(OPTIONS.boot_signer_args)
1595 cmd.extend([path, img.name,
1596 info_dict["verity_key"] + ".pk8",
1597 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001598 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001599
Tao Baod95e9fd2015-03-29 23:07:41 -07001600 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001601 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001602 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001603 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001604 # We have switched from the prebuilt futility binary to using the tool
1605 # (futility-host) built from the source. Override the setting in the old
1606 # TF.zip.
1607 futility = info_dict["futility"]
1608 if futility.startswith("prebuilts/"):
1609 futility = "futility-host"
1610 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001611 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001612 info_dict["vboot_key"] + ".vbprivk",
1613 info_dict["vboot_subkey"] + ".vbprivk",
1614 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001615 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001616 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001617
Tao Baof3282b42015-04-01 11:21:55 -07001618 # Clean up the temp files.
1619 img_unsigned.close()
1620 img_keyblock.close()
1621
David Zeuthen8fecb282017-12-01 16:24:01 -05001622 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001623 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001624 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001625 if partition_name == "recovery":
1626 part_size = info_dict["recovery_size"]
1627 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001628 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001629 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001630 "--partition_size", str(part_size), "--partition_name",
1631 partition_name]
1632 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001633 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001634 if args and args.strip():
1635 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001636 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001637
1638 img.seek(os.SEEK_SET, 0)
1639 data = img.read()
1640
1641 if has_ramdisk:
1642 ramdisk_img.close()
1643 img.close()
1644
1645 return data
1646
1647
Doug Zongkerd5131602012-08-02 14:46:42 -07001648def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001649 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001650 """Return a File object with the desired bootable image.
1651
1652 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1653 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1654 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001655
Doug Zongker55d93282011-01-25 17:03:34 -08001656 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1657 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001658 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001659 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001660
1661 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1662 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001663 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001664 return File.FromLocalFile(name, prebuilt_path)
1665
Tao Bao32fcdab2018-10-12 10:30:39 -07001666 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001667
1668 if info_dict is None:
1669 info_dict = OPTIONS.info_dict
1670
1671 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001672 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1673 # for recovery.
1674 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1675 prebuilt_name != "boot.img" or
1676 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001677
Doug Zongker6f1d0312014-08-22 08:07:12 -07001678 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001679 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001680 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001681 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001682 if data:
1683 return File(name, data)
1684 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001685
Doug Zongkereef39442009-04-02 12:14:19 -07001686
Steve Mucklee1b10862019-07-10 10:49:37 -07001687def _BuildVendorBootImage(sourcedir, info_dict=None):
1688 """Build a vendor boot image from the specified sourcedir.
1689
1690 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1691 turn them into a vendor boot image.
1692
1693 Return the image data, or None if sourcedir does not appear to contains files
1694 for building the requested image.
1695 """
1696
1697 if info_dict is None:
1698 info_dict = OPTIONS.info_dict
1699
1700 img = tempfile.NamedTemporaryFile()
1701
J. Avila98cd4cc2020-06-10 20:09:10 +00001702 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1703 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001704
1705 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1706 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1707
1708 cmd = [mkbootimg]
1709
1710 fn = os.path.join(sourcedir, "dtb")
1711 if os.access(fn, os.F_OK):
1712 cmd.append("--dtb")
1713 cmd.append(fn)
1714
1715 fn = os.path.join(sourcedir, "vendor_cmdline")
1716 if os.access(fn, os.F_OK):
1717 cmd.append("--vendor_cmdline")
1718 cmd.append(open(fn).read().rstrip("\n"))
1719
1720 fn = os.path.join(sourcedir, "base")
1721 if os.access(fn, os.F_OK):
1722 cmd.append("--base")
1723 cmd.append(open(fn).read().rstrip("\n"))
1724
1725 fn = os.path.join(sourcedir, "pagesize")
1726 if os.access(fn, os.F_OK):
1727 cmd.append("--pagesize")
1728 cmd.append(open(fn).read().rstrip("\n"))
1729
1730 args = info_dict.get("mkbootimg_args")
1731 if args and args.strip():
1732 cmd.extend(shlex.split(args))
1733
1734 args = info_dict.get("mkbootimg_version_args")
1735 if args and args.strip():
1736 cmd.extend(shlex.split(args))
1737
1738 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1739 cmd.extend(["--vendor_boot", img.name])
1740
Devin Moore50509012021-01-13 10:45:04 -08001741 fn = os.path.join(sourcedir, "vendor_bootconfig")
1742 if os.access(fn, os.F_OK):
1743 cmd.append("--vendor_bootconfig")
1744 cmd.append(fn)
1745
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001746 ramdisk_fragment_imgs = []
1747 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1748 if os.access(fn, os.F_OK):
1749 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1750 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001751 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1752 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001753 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001754 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1755 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001756 # Use prebuilt image if found, else create ramdisk from supplied files.
1757 if os.access(fn, os.F_OK):
1758 ramdisk_fragment_pathname = fn
1759 else:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001760 ramdisk_fragment_root = os.path.join(
1761 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1762 ramdisk_fragment_img = _MakeRamdisk(
1763 ramdisk_fragment_root, lz4_ramdisks=use_lz4)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001764 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1765 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1766 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1767
Steve Mucklee1b10862019-07-10 10:49:37 -07001768 RunAndCheckOutput(cmd)
1769
1770 # AVB: if enabled, calculate and add hash.
1771 if info_dict.get("avb_enable") == "true":
1772 avbtool = info_dict["avb_avbtool"]
1773 part_size = info_dict["vendor_boot_size"]
1774 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001775 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001776 AppendAVBSigningArgs(cmd, "vendor_boot")
1777 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1778 if args and args.strip():
1779 cmd.extend(shlex.split(args))
1780 RunAndCheckOutput(cmd)
1781
1782 img.seek(os.SEEK_SET, 0)
1783 data = img.read()
1784
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001785 for f in ramdisk_fragment_imgs:
1786 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001787 ramdisk_img.close()
1788 img.close()
1789
1790 return data
1791
1792
1793def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1794 info_dict=None):
1795 """Return a File object with the desired vendor boot image.
1796
1797 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1798 the source files in 'unpack_dir'/'tree_subdir'."""
1799
1800 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1801 if os.path.exists(prebuilt_path):
1802 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1803 return File.FromLocalFile(name, prebuilt_path)
1804
1805 logger.info("building image from target_files %s...", tree_subdir)
1806
1807 if info_dict is None:
1808 info_dict = OPTIONS.info_dict
1809
Kelvin Zhang0876c412020-06-23 15:06:58 -04001810 data = _BuildVendorBootImage(
1811 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001812 if data:
1813 return File(name, data)
1814 return None
1815
1816
Narayan Kamatha07bf042017-08-14 14:49:21 +01001817def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001818 """Gunzips the given gzip compressed file to a given output file."""
1819 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001820 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001821 shutil.copyfileobj(in_file, out_file)
1822
1823
Tao Bao0ff15de2019-03-20 11:26:06 -07001824def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001825 """Unzips the archive to the given directory.
1826
1827 Args:
1828 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001829 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001830 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1831 archvie. Non-matching patterns will be filtered out. If there's no match
1832 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001833 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001834 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001835 if patterns is not None:
1836 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001837 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001838 names = input_zip.namelist()
1839 filtered = [
1840 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1841
1842 # There isn't any matching files. Don't unzip anything.
1843 if not filtered:
1844 return
1845 cmd.extend(filtered)
1846
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001847 RunAndCheckOutput(cmd)
1848
1849
Doug Zongker75f17362009-12-08 13:46:44 -08001850def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001851 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001852
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001853 Args:
1854 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1855 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1856
1857 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1858 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001859
Tao Bao1c830bf2017-12-25 10:43:47 -08001860 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001861 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001862 """
Doug Zongkereef39442009-04-02 12:14:19 -07001863
Tao Bao1c830bf2017-12-25 10:43:47 -08001864 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001865 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1866 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001867 UnzipToDir(m.group(1), tmp, pattern)
1868 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001869 filename = m.group(1)
1870 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001871 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001872
Tao Baodba59ee2018-01-09 13:21:02 -08001873 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001874
1875
Yifan Hong8a66a712019-04-04 15:37:57 -07001876def GetUserImage(which, tmpdir, input_zip,
1877 info_dict=None,
1878 allow_shared_blocks=None,
1879 hashtree_info_generator=None,
1880 reset_file_map=False):
1881 """Returns an Image object suitable for passing to BlockImageDiff.
1882
1883 This function loads the specified image from the given path. If the specified
1884 image is sparse, it also performs additional processing for OTA purpose. For
1885 example, it always adds block 0 to clobbered blocks list. It also detects
1886 files that cannot be reconstructed from the block list, for whom we should
1887 avoid applying imgdiff.
1888
1889 Args:
1890 which: The partition name.
1891 tmpdir: The directory that contains the prebuilt image and block map file.
1892 input_zip: The target-files ZIP archive.
1893 info_dict: The dict to be looked up for relevant info.
1894 allow_shared_blocks: If image is sparse, whether having shared blocks is
1895 allowed. If none, it is looked up from info_dict.
1896 hashtree_info_generator: If present and image is sparse, generates the
1897 hashtree_info for this sparse image.
1898 reset_file_map: If true and image is sparse, reset file map before returning
1899 the image.
1900 Returns:
1901 A Image object. If it is a sparse image and reset_file_map is False, the
1902 image will have file_map info loaded.
1903 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001904 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001905 info_dict = LoadInfoDict(input_zip)
1906
1907 is_sparse = info_dict.get("extfs_sparse_flag")
1908
1909 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1910 # shared blocks (i.e. some blocks will show up in multiple files' block
1911 # list). We can only allocate such shared blocks to the first "owner", and
1912 # disable imgdiff for all later occurrences.
1913 if allow_shared_blocks is None:
1914 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1915
1916 if is_sparse:
1917 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1918 hashtree_info_generator)
1919 if reset_file_map:
1920 img.ResetFileMap()
1921 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001922 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001923
1924
1925def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1926 """Returns a Image object suitable for passing to BlockImageDiff.
1927
1928 This function loads the specified non-sparse image from the given path.
1929
1930 Args:
1931 which: The partition name.
1932 tmpdir: The directory that contains the prebuilt image and block map file.
1933 Returns:
1934 A Image object.
1935 """
1936 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1937 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1938
1939 # The image and map files must have been created prior to calling
1940 # ota_from_target_files.py (since LMP).
1941 assert os.path.exists(path) and os.path.exists(mappath)
1942
Tianjie Xu41976c72019-07-03 13:57:01 -07001943 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1944
Yifan Hong8a66a712019-04-04 15:37:57 -07001945
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001946def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1947 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001948 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1949
1950 This function loads the specified sparse image from the given path, and
1951 performs additional processing for OTA purpose. For example, it always adds
1952 block 0 to clobbered blocks list. It also detects files that cannot be
1953 reconstructed from the block list, for whom we should avoid applying imgdiff.
1954
1955 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001956 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001957 tmpdir: The directory that contains the prebuilt image and block map file.
1958 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001959 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001960 hashtree_info_generator: If present, generates the hashtree_info for this
1961 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001962 Returns:
1963 A SparseImage object, with file_map info loaded.
1964 """
Tao Baoc765cca2018-01-31 17:32:40 -08001965 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1966 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1967
1968 # The image and map files must have been created prior to calling
1969 # ota_from_target_files.py (since LMP).
1970 assert os.path.exists(path) and os.path.exists(mappath)
1971
1972 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1973 # it to clobbered_blocks so that it will be written to the target
1974 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1975 clobbered_blocks = "0"
1976
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001977 image = sparse_img.SparseImage(
1978 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1979 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001980
1981 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1982 # if they contain all zeros. We can't reconstruct such a file from its block
1983 # list. Tag such entries accordingly. (Bug: 65213616)
1984 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001985 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001986 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001987 continue
1988
Tom Cherryd14b8952018-08-09 14:26:00 -07001989 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1990 # filename listed in system.map may contain an additional leading slash
1991 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1992 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001993 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001994 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001995 arcname = entry.lstrip('/')
1996 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001997 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001998 else:
1999 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002000
2001 assert arcname in input_zip.namelist(), \
2002 "Failed to find the ZIP entry for {}".format(entry)
2003
Tao Baoc765cca2018-01-31 17:32:40 -08002004 info = input_zip.getinfo(arcname)
2005 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002006
2007 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002008 # image, check the original block list to determine its completeness. Note
2009 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002010 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002011 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002012
Tao Baoc765cca2018-01-31 17:32:40 -08002013 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2014 ranges.extra['incomplete'] = True
2015
2016 return image
2017
2018
Doug Zongkereef39442009-04-02 12:14:19 -07002019def GetKeyPasswords(keylist):
2020 """Given a list of keys, prompt the user to enter passwords for
2021 those which require them. Return a {key: password} dict. password
2022 will be None if the key has no password."""
2023
Doug Zongker8ce7c252009-05-22 13:34:54 -07002024 no_passwords = []
2025 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002026 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002027 devnull = open("/dev/null", "w+b")
2028 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002029 # We don't need a password for things that aren't really keys.
2030 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002031 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002032 continue
2033
T.R. Fullhart37e10522013-03-18 10:31:26 -07002034 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002035 "-inform", "DER", "-nocrypt"],
2036 stdin=devnull.fileno(),
2037 stdout=devnull.fileno(),
2038 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002039 p.communicate()
2040 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002041 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002042 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002043 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002044 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2045 "-inform", "DER", "-passin", "pass:"],
2046 stdin=devnull.fileno(),
2047 stdout=devnull.fileno(),
2048 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002049 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002050 if p.returncode == 0:
2051 # Encrypted key with empty string as password.
2052 key_passwords[k] = ''
2053 elif stderr.startswith('Error decrypting key'):
2054 # Definitely encrypted key.
2055 # It would have said "Error reading key" if it didn't parse correctly.
2056 need_passwords.append(k)
2057 else:
2058 # Potentially, a type of key that openssl doesn't understand.
2059 # We'll let the routines in signapk.jar handle it.
2060 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002061 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002062
T.R. Fullhart37e10522013-03-18 10:31:26 -07002063 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002064 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002065 return key_passwords
2066
2067
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002068def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002069 """Gets the minSdkVersion declared in the APK.
2070
changho.shin0f125362019-07-08 10:59:00 +09002071 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002072 This can be both a decimal number (API Level) or a codename.
2073
2074 Args:
2075 apk_name: The APK filename.
2076
2077 Returns:
2078 The parsed SDK version string.
2079
2080 Raises:
2081 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002082 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002083 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002084 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002085 stderr=subprocess.PIPE)
2086 stdoutdata, stderrdata = proc.communicate()
2087 if proc.returncode != 0:
2088 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002089 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002090 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002091
Tao Baof47bf0f2018-03-21 23:28:51 -07002092 for line in stdoutdata.split("\n"):
2093 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002094 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2095 if m:
2096 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002097 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002098
2099
2100def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002101 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002102
Tao Baof47bf0f2018-03-21 23:28:51 -07002103 If minSdkVersion is set to a codename, it is translated to a number using the
2104 provided map.
2105
2106 Args:
2107 apk_name: The APK filename.
2108
2109 Returns:
2110 The parsed SDK version number.
2111
2112 Raises:
2113 ExternalError: On failing to get the min SDK version number.
2114 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002115 version = GetMinSdkVersion(apk_name)
2116 try:
2117 return int(version)
2118 except ValueError:
2119 # Not a decimal number. Codename?
2120 if version in codename_to_api_level_map:
2121 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002122 raise ExternalError(
2123 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2124 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002125
2126
2127def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002128 codename_to_api_level_map=None, whole_file=False,
2129 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002130 """Sign the input_name zip/jar/apk, producing output_name. Use the
2131 given key and password (the latter may be None if the key does not
2132 have a password.
2133
Doug Zongker951495f2009-08-14 12:44:19 -07002134 If whole_file is true, use the "-w" option to SignApk to embed a
2135 signature that covers the whole file in the archive comment of the
2136 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002137
2138 min_api_level is the API Level (int) of the oldest platform this file may end
2139 up on. If not specified for an APK, the API Level is obtained by interpreting
2140 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2141
2142 codename_to_api_level_map is needed to translate the codename which may be
2143 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002144
2145 Caller may optionally specify extra args to be passed to SignApk, which
2146 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002147 """
Tao Bao76def242017-11-21 09:25:31 -08002148 if codename_to_api_level_map is None:
2149 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002150 if extra_signapk_args is None:
2151 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002152
Alex Klyubin9667b182015-12-10 13:38:50 -08002153 java_library_path = os.path.join(
2154 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2155
Tao Baoe95540e2016-11-08 12:08:53 -08002156 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2157 ["-Djava.library.path=" + java_library_path,
2158 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002159 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002160 if whole_file:
2161 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002162
2163 min_sdk_version = min_api_level
2164 if min_sdk_version is None:
2165 if not whole_file:
2166 min_sdk_version = GetMinSdkVersionInt(
2167 input_name, codename_to_api_level_map)
2168 if min_sdk_version is not None:
2169 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2170
T.R. Fullhart37e10522013-03-18 10:31:26 -07002171 cmd.extend([key + OPTIONS.public_key_suffix,
2172 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002173 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002174
Tao Bao73dd4f42018-10-04 16:25:33 -07002175 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002176 if password is not None:
2177 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002178 stdoutdata, _ = proc.communicate(password)
2179 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002180 raise ExternalError(
2181 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002182 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002183
Doug Zongkereef39442009-04-02 12:14:19 -07002184
Doug Zongker37974732010-09-16 17:44:38 -07002185def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002186 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002187
Tao Bao9dd909e2017-11-14 11:27:32 -08002188 For non-AVB images, raise exception if the data is too big. Print a warning
2189 if the data is nearing the maximum size.
2190
2191 For AVB images, the actual image size should be identical to the limit.
2192
2193 Args:
2194 data: A string that contains all the data for the partition.
2195 target: The partition name. The ".img" suffix is optional.
2196 info_dict: The dict to be looked up for relevant info.
2197 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002198 if target.endswith(".img"):
2199 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002200 mount_point = "/" + target
2201
Ying Wangf8824af2014-06-03 14:07:27 -07002202 fs_type = None
2203 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002204 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 if mount_point == "/userdata":
2206 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002207 p = info_dict["fstab"][mount_point]
2208 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002209 device = p.device
2210 if "/" in device:
2211 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002212 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002213 if not fs_type or not limit:
2214 return
Doug Zongkereef39442009-04-02 12:14:19 -07002215
Andrew Boie0f9aec82012-02-14 09:32:52 -08002216 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002217 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2218 # path.
2219 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2220 if size != limit:
2221 raise ExternalError(
2222 "Mismatching image size for %s: expected %d actual %d" % (
2223 target, limit, size))
2224 else:
2225 pct = float(size) * 100.0 / limit
2226 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2227 if pct >= 99.0:
2228 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002229
2230 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002231 logger.warning("\n WARNING: %s\n", msg)
2232 else:
2233 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002234
2235
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002236def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002237 """Parses the APK certs info from a given target-files zip.
2238
2239 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2240 tuple with the following elements: (1) a dictionary that maps packages to
2241 certs (based on the "certificate" and "private_key" attributes in the file;
2242 (2) a string representing the extension of compressed APKs in the target files
2243 (e.g ".gz", ".bro").
2244
2245 Args:
2246 tf_zip: The input target_files ZipFile (already open).
2247
2248 Returns:
2249 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2250 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2251 no compressed APKs.
2252 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002253 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002254 compressed_extension = None
2255
Tao Bao0f990332017-09-08 19:02:54 -07002256 # META/apkcerts.txt contains the info for _all_ the packages known at build
2257 # time. Filter out the ones that are not installed.
2258 installed_files = set()
2259 for name in tf_zip.namelist():
2260 basename = os.path.basename(name)
2261 if basename:
2262 installed_files.add(basename)
2263
Tao Baoda30cfa2017-12-01 16:19:46 -08002264 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002265 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002266 if not line:
2267 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002268 m = re.match(
2269 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002270 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2271 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002272 line)
2273 if not m:
2274 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002275
Tao Bao818ddf52018-01-05 11:17:34 -08002276 matches = m.groupdict()
2277 cert = matches["CERT"]
2278 privkey = matches["PRIVKEY"]
2279 name = matches["NAME"]
2280 this_compressed_extension = matches["COMPRESSED"]
2281
2282 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2283 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2284 if cert in SPECIAL_CERT_STRINGS and not privkey:
2285 certmap[name] = cert
2286 elif (cert.endswith(OPTIONS.public_key_suffix) and
2287 privkey.endswith(OPTIONS.private_key_suffix) and
2288 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2289 certmap[name] = cert[:-public_key_suffix_len]
2290 else:
2291 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2292
2293 if not this_compressed_extension:
2294 continue
2295
2296 # Only count the installed files.
2297 filename = name + '.' + this_compressed_extension
2298 if filename not in installed_files:
2299 continue
2300
2301 # Make sure that all the values in the compression map have the same
2302 # extension. We don't support multiple compression methods in the same
2303 # system image.
2304 if compressed_extension:
2305 if this_compressed_extension != compressed_extension:
2306 raise ValueError(
2307 "Multiple compressed extensions: {} vs {}".format(
2308 compressed_extension, this_compressed_extension))
2309 else:
2310 compressed_extension = this_compressed_extension
2311
2312 return (certmap,
2313 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002314
2315
Doug Zongkereef39442009-04-02 12:14:19 -07002316COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002317Global options
2318
2319 -p (--path) <dir>
2320 Prepend <dir>/bin to the list of places to search for binaries run by this
2321 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002322
Doug Zongker05d3dea2009-06-22 11:32:31 -07002323 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002324 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002325
Tao Bao30df8b42018-04-23 15:32:53 -07002326 -x (--extra) <key=value>
2327 Add a key/value pair to the 'extras' dict, which device-specific extension
2328 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002329
Doug Zongkereef39442009-04-02 12:14:19 -07002330 -v (--verbose)
2331 Show command lines being executed.
2332
2333 -h (--help)
2334 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002335
2336 --logfile <file>
2337 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002338"""
2339
Kelvin Zhang0876c412020-06-23 15:06:58 -04002340
Doug Zongkereef39442009-04-02 12:14:19 -07002341def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002342 print(docstring.rstrip("\n"))
2343 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002344
2345
2346def ParseOptions(argv,
2347 docstring,
2348 extra_opts="", extra_long_opts=(),
2349 extra_option_handler=None):
2350 """Parse the options in argv and return any arguments that aren't
2351 flags. docstring is the calling module's docstring, to be displayed
2352 for errors and -h. extra_opts and extra_long_opts are for flags
2353 defined by the caller, which are processed by passing them to
2354 extra_option_handler."""
2355
2356 try:
2357 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002358 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002359 ["help", "verbose", "path=", "signapk_path=",
2360 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002361 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002362 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2363 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002364 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2365 "aftl_key_path=", "aftl_manufacturer_key_path=",
2366 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002367 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002368 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002369 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002370 sys.exit(2)
2371
Doug Zongkereef39442009-04-02 12:14:19 -07002372 for o, a in opts:
2373 if o in ("-h", "--help"):
2374 Usage(docstring)
2375 sys.exit()
2376 elif o in ("-v", "--verbose"):
2377 OPTIONS.verbose = True
2378 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002379 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002380 elif o in ("--signapk_path",):
2381 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002382 elif o in ("--signapk_shared_library_path",):
2383 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002384 elif o in ("--extra_signapk_args",):
2385 OPTIONS.extra_signapk_args = shlex.split(a)
2386 elif o in ("--java_path",):
2387 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002388 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002389 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002390 elif o in ("--android_jar_path",):
2391 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002392 elif o in ("--public_key_suffix",):
2393 OPTIONS.public_key_suffix = a
2394 elif o in ("--private_key_suffix",):
2395 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002396 elif o in ("--boot_signer_path",):
2397 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002398 elif o in ("--boot_signer_args",):
2399 OPTIONS.boot_signer_args = shlex.split(a)
2400 elif o in ("--verity_signer_path",):
2401 OPTIONS.verity_signer_path = a
2402 elif o in ("--verity_signer_args",):
2403 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002404 elif o in ("--aftl_tool_path",):
2405 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002406 elif o in ("--aftl_server",):
2407 OPTIONS.aftl_server = a
2408 elif o in ("--aftl_key_path",):
2409 OPTIONS.aftl_key_path = a
2410 elif o in ("--aftl_manufacturer_key_path",):
2411 OPTIONS.aftl_manufacturer_key_path = a
2412 elif o in ("--aftl_signer_helper",):
2413 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002414 elif o in ("-s", "--device_specific"):
2415 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002416 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002417 key, value = a.split("=", 1)
2418 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002419 elif o in ("--logfile",):
2420 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002421 else:
2422 if extra_option_handler is None or not extra_option_handler(o, a):
2423 assert False, "unknown option \"%s\"" % (o,)
2424
Doug Zongker85448772014-09-09 14:59:20 -07002425 if OPTIONS.search_path:
2426 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2427 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002428
2429 return args
2430
2431
Tao Bao4c851b12016-09-19 13:54:38 -07002432def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002433 """Make a temp file and add it to the list of things to be deleted
2434 when Cleanup() is called. Return the filename."""
2435 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2436 os.close(fd)
2437 OPTIONS.tempfiles.append(fn)
2438 return fn
2439
2440
Tao Bao1c830bf2017-12-25 10:43:47 -08002441def MakeTempDir(prefix='tmp', suffix=''):
2442 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2443
2444 Returns:
2445 The absolute pathname of the new directory.
2446 """
2447 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2448 OPTIONS.tempfiles.append(dir_name)
2449 return dir_name
2450
2451
Doug Zongkereef39442009-04-02 12:14:19 -07002452def Cleanup():
2453 for i in OPTIONS.tempfiles:
2454 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002455 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002456 else:
2457 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002458 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002459
2460
2461class PasswordManager(object):
2462 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002463 self.editor = os.getenv("EDITOR")
2464 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002465
2466 def GetPasswords(self, items):
2467 """Get passwords corresponding to each string in 'items',
2468 returning a dict. (The dict may have keys in addition to the
2469 values in 'items'.)
2470
2471 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2472 user edit that file to add more needed passwords. If no editor is
2473 available, or $ANDROID_PW_FILE isn't define, prompts the user
2474 interactively in the ordinary way.
2475 """
2476
2477 current = self.ReadFile()
2478
2479 first = True
2480 while True:
2481 missing = []
2482 for i in items:
2483 if i not in current or not current[i]:
2484 missing.append(i)
2485 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002486 if not missing:
2487 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002488
2489 for i in missing:
2490 current[i] = ""
2491
2492 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002493 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002494 if sys.version_info[0] >= 3:
2495 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002496 answer = raw_input("try to edit again? [y]> ").strip()
2497 if answer and answer[0] not in 'yY':
2498 raise RuntimeError("key passwords unavailable")
2499 first = False
2500
2501 current = self.UpdateAndReadFile(current)
2502
Kelvin Zhang0876c412020-06-23 15:06:58 -04002503 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002504 """Prompt the user to enter a value (password) for each key in
2505 'current' whose value is fales. Returns a new dict with all the
2506 values.
2507 """
2508 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002509 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002510 if v:
2511 result[k] = v
2512 else:
2513 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002514 result[k] = getpass.getpass(
2515 "Enter password for %s key> " % k).strip()
2516 if result[k]:
2517 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002518 return result
2519
2520 def UpdateAndReadFile(self, current):
2521 if not self.editor or not self.pwfile:
2522 return self.PromptResult(current)
2523
2524 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002525 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002526 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2527 f.write("# (Additional spaces are harmless.)\n\n")
2528
2529 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002530 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002531 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002532 f.write("[[[ %s ]]] %s\n" % (v, k))
2533 if not v and first_line is None:
2534 # position cursor on first line with no password.
2535 first_line = i + 4
2536 f.close()
2537
Tao Bao986ee862018-10-04 15:46:16 -07002538 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002539
2540 return self.ReadFile()
2541
2542 def ReadFile(self):
2543 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002544 if self.pwfile is None:
2545 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002546 try:
2547 f = open(self.pwfile, "r")
2548 for line in f:
2549 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002550 if not line or line[0] == '#':
2551 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002552 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2553 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002554 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002555 else:
2556 result[m.group(2)] = m.group(1)
2557 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002558 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002559 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002560 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002561 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002562
2563
Dan Albert8e0178d2015-01-27 15:53:15 -08002564def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2565 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002566
2567 # http://b/18015246
2568 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2569 # for files larger than 2GiB. We can work around this by adjusting their
2570 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2571 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2572 # it isn't clear to me exactly what circumstances cause this).
2573 # `zipfile.write()` must be used directly to work around this.
2574 #
2575 # This mess can be avoided if we port to python3.
2576 saved_zip64_limit = zipfile.ZIP64_LIMIT
2577 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2578
2579 if compress_type is None:
2580 compress_type = zip_file.compression
2581 if arcname is None:
2582 arcname = filename
2583
2584 saved_stat = os.stat(filename)
2585
2586 try:
2587 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2588 # file to be zipped and reset it when we're done.
2589 os.chmod(filename, perms)
2590
2591 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002592 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2593 # intentional. zip stores datetimes in local time without a time zone
2594 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2595 # in the zip archive.
2596 local_epoch = datetime.datetime.fromtimestamp(0)
2597 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002598 os.utime(filename, (timestamp, timestamp))
2599
2600 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2601 finally:
2602 os.chmod(filename, saved_stat.st_mode)
2603 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2604 zipfile.ZIP64_LIMIT = saved_zip64_limit
2605
2606
Tao Bao58c1b962015-05-20 09:32:18 -07002607def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002608 compress_type=None):
2609 """Wrap zipfile.writestr() function to work around the zip64 limit.
2610
2611 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2612 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2613 when calling crc32(bytes).
2614
2615 But it still works fine to write a shorter string into a large zip file.
2616 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2617 when we know the string won't be too long.
2618 """
2619
2620 saved_zip64_limit = zipfile.ZIP64_LIMIT
2621 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2622
2623 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2624 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002625 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002626 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002627 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002628 else:
Tao Baof3282b42015-04-01 11:21:55 -07002629 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002630 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2631 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2632 # such a case (since
2633 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2634 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2635 # permission bits. We follow the logic in Python 3 to get consistent
2636 # behavior between using the two versions.
2637 if not zinfo.external_attr:
2638 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002639
2640 # If compress_type is given, it overrides the value in zinfo.
2641 if compress_type is not None:
2642 zinfo.compress_type = compress_type
2643
Tao Bao58c1b962015-05-20 09:32:18 -07002644 # If perms is given, it has a priority.
2645 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002646 # If perms doesn't set the file type, mark it as a regular file.
2647 if perms & 0o770000 == 0:
2648 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002649 zinfo.external_attr = perms << 16
2650
Tao Baof3282b42015-04-01 11:21:55 -07002651 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002652 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2653
Dan Albert8b72aef2015-03-23 19:13:21 -07002654 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002655 zipfile.ZIP64_LIMIT = saved_zip64_limit
2656
2657
Tao Bao89d7ab22017-12-14 17:05:33 -08002658def ZipDelete(zip_filename, entries):
2659 """Deletes entries from a ZIP file.
2660
2661 Since deleting entries from a ZIP file is not supported, it shells out to
2662 'zip -d'.
2663
2664 Args:
2665 zip_filename: The name of the ZIP file.
2666 entries: The name of the entry, or the list of names to be deleted.
2667
2668 Raises:
2669 AssertionError: In case of non-zero return from 'zip'.
2670 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002671 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002672 entries = [entries]
2673 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002674 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002675
2676
Tao Baof3282b42015-04-01 11:21:55 -07002677def ZipClose(zip_file):
2678 # http://b/18015246
2679 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2680 # central directory.
2681 saved_zip64_limit = zipfile.ZIP64_LIMIT
2682 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2683
2684 zip_file.close()
2685
2686 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002687
2688
2689class DeviceSpecificParams(object):
2690 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002691
Doug Zongker05d3dea2009-06-22 11:32:31 -07002692 def __init__(self, **kwargs):
2693 """Keyword arguments to the constructor become attributes of this
2694 object, which is passed to all functions in the device-specific
2695 module."""
Tao Bao38884282019-07-10 22:20:56 -07002696 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002697 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002698 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002699
2700 if self.module is None:
2701 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002702 if not path:
2703 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002704 try:
2705 if os.path.isdir(path):
2706 info = imp.find_module("releasetools", [path])
2707 else:
2708 d, f = os.path.split(path)
2709 b, x = os.path.splitext(f)
2710 if x == ".py":
2711 f = b
2712 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002713 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002714 self.module = imp.load_module("device_specific", *info)
2715 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002716 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002717
2718 def _DoCall(self, function_name, *args, **kwargs):
2719 """Call the named function in the device-specific module, passing
2720 the given args and kwargs. The first argument to the call will be
2721 the DeviceSpecific object itself. If there is no module, or the
2722 module does not define the function, return the value of the
2723 'default' kwarg (which itself defaults to None)."""
2724 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002725 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002726 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2727
2728 def FullOTA_Assertions(self):
2729 """Called after emitting the block of assertions at the top of a
2730 full OTA package. Implementations can add whatever additional
2731 assertions they like."""
2732 return self._DoCall("FullOTA_Assertions")
2733
Doug Zongkere5ff5902012-01-17 10:55:37 -08002734 def FullOTA_InstallBegin(self):
2735 """Called at the start of full OTA installation."""
2736 return self._DoCall("FullOTA_InstallBegin")
2737
Yifan Hong10c530d2018-12-27 17:34:18 -08002738 def FullOTA_GetBlockDifferences(self):
2739 """Called during full OTA installation and verification.
2740 Implementation should return a list of BlockDifference objects describing
2741 the update on each additional partitions.
2742 """
2743 return self._DoCall("FullOTA_GetBlockDifferences")
2744
Doug Zongker05d3dea2009-06-22 11:32:31 -07002745 def FullOTA_InstallEnd(self):
2746 """Called at the end of full OTA installation; typically this is
2747 used to install the image for the device's baseband processor."""
2748 return self._DoCall("FullOTA_InstallEnd")
2749
2750 def IncrementalOTA_Assertions(self):
2751 """Called after emitting the block of assertions at the top of an
2752 incremental OTA package. Implementations can add whatever
2753 additional assertions they like."""
2754 return self._DoCall("IncrementalOTA_Assertions")
2755
Doug Zongkere5ff5902012-01-17 10:55:37 -08002756 def IncrementalOTA_VerifyBegin(self):
2757 """Called at the start of the verification phase of incremental
2758 OTA installation; additional checks can be placed here to abort
2759 the script before any changes are made."""
2760 return self._DoCall("IncrementalOTA_VerifyBegin")
2761
Doug Zongker05d3dea2009-06-22 11:32:31 -07002762 def IncrementalOTA_VerifyEnd(self):
2763 """Called at the end of the verification phase of incremental OTA
2764 installation; additional checks can be placed here to abort the
2765 script before any changes are made."""
2766 return self._DoCall("IncrementalOTA_VerifyEnd")
2767
Doug Zongkere5ff5902012-01-17 10:55:37 -08002768 def IncrementalOTA_InstallBegin(self):
2769 """Called at the start of incremental OTA installation (after
2770 verification is complete)."""
2771 return self._DoCall("IncrementalOTA_InstallBegin")
2772
Yifan Hong10c530d2018-12-27 17:34:18 -08002773 def IncrementalOTA_GetBlockDifferences(self):
2774 """Called during incremental OTA installation and verification.
2775 Implementation should return a list of BlockDifference objects describing
2776 the update on each additional partitions.
2777 """
2778 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2779
Doug Zongker05d3dea2009-06-22 11:32:31 -07002780 def IncrementalOTA_InstallEnd(self):
2781 """Called at the end of incremental OTA installation; typically
2782 this is used to install the image for the device's baseband
2783 processor."""
2784 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002785
Tao Bao9bc6bb22015-11-09 16:58:28 -08002786 def VerifyOTA_Assertions(self):
2787 return self._DoCall("VerifyOTA_Assertions")
2788
Tao Bao76def242017-11-21 09:25:31 -08002789
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002790class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002791 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002792 self.name = name
2793 self.data = data
2794 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002795 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002796 self.sha1 = sha1(data).hexdigest()
2797
2798 @classmethod
2799 def FromLocalFile(cls, name, diskname):
2800 f = open(diskname, "rb")
2801 data = f.read()
2802 f.close()
2803 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002804
2805 def WriteToTemp(self):
2806 t = tempfile.NamedTemporaryFile()
2807 t.write(self.data)
2808 t.flush()
2809 return t
2810
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002811 def WriteToDir(self, d):
2812 with open(os.path.join(d, self.name), "wb") as fp:
2813 fp.write(self.data)
2814
Geremy Condra36bd3652014-02-06 19:45:10 -08002815 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002816 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002817
Tao Bao76def242017-11-21 09:25:31 -08002818
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002819DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002820 ".gz": "imgdiff",
2821 ".zip": ["imgdiff", "-z"],
2822 ".jar": ["imgdiff", "-z"],
2823 ".apk": ["imgdiff", "-z"],
2824 ".img": "imgdiff",
2825}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002826
Tao Bao76def242017-11-21 09:25:31 -08002827
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002828class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002829 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002830 self.tf = tf
2831 self.sf = sf
2832 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002833 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002834
2835 def ComputePatch(self):
2836 """Compute the patch (as a string of data) needed to turn sf into
2837 tf. Returns the same tuple as GetPatch()."""
2838
2839 tf = self.tf
2840 sf = self.sf
2841
Doug Zongker24cd2802012-08-14 16:36:15 -07002842 if self.diff_program:
2843 diff_program = self.diff_program
2844 else:
2845 ext = os.path.splitext(tf.name)[1]
2846 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002847
2848 ttemp = tf.WriteToTemp()
2849 stemp = sf.WriteToTemp()
2850
2851 ext = os.path.splitext(tf.name)[1]
2852
2853 try:
2854 ptemp = tempfile.NamedTemporaryFile()
2855 if isinstance(diff_program, list):
2856 cmd = copy.copy(diff_program)
2857 else:
2858 cmd = [diff_program]
2859 cmd.append(stemp.name)
2860 cmd.append(ttemp.name)
2861 cmd.append(ptemp.name)
2862 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002863 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002864
Doug Zongkerf8340082014-08-05 10:39:37 -07002865 def run():
2866 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002867 if e:
2868 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002869 th = threading.Thread(target=run)
2870 th.start()
2871 th.join(timeout=300) # 5 mins
2872 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002873 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002874 p.terminate()
2875 th.join(5)
2876 if th.is_alive():
2877 p.kill()
2878 th.join()
2879
Tianjie Xua2a9f992018-01-05 15:15:54 -08002880 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002881 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002882 self.patch = None
2883 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002884 diff = ptemp.read()
2885 finally:
2886 ptemp.close()
2887 stemp.close()
2888 ttemp.close()
2889
2890 self.patch = diff
2891 return self.tf, self.sf, self.patch
2892
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002893 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002894 """Returns a tuple of (target_file, source_file, patch_data).
2895
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002896 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002897 computing the patch failed.
2898 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002899 return self.tf, self.sf, self.patch
2900
2901
2902def ComputeDifferences(diffs):
2903 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002904 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002905
2906 # Do the largest files first, to try and reduce the long-pole effect.
2907 by_size = [(i.tf.size, i) for i in diffs]
2908 by_size.sort(reverse=True)
2909 by_size = [i[1] for i in by_size]
2910
2911 lock = threading.Lock()
2912 diff_iter = iter(by_size) # accessed under lock
2913
2914 def worker():
2915 try:
2916 lock.acquire()
2917 for d in diff_iter:
2918 lock.release()
2919 start = time.time()
2920 d.ComputePatch()
2921 dur = time.time() - start
2922 lock.acquire()
2923
2924 tf, sf, patch = d.GetPatch()
2925 if sf.name == tf.name:
2926 name = tf.name
2927 else:
2928 name = "%s (%s)" % (tf.name, sf.name)
2929 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002930 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002931 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002932 logger.info(
2933 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2934 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002935 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002936 except Exception:
2937 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002938 raise
2939
2940 # start worker threads; wait for them all to finish.
2941 threads = [threading.Thread(target=worker)
2942 for i in range(OPTIONS.worker_threads)]
2943 for th in threads:
2944 th.start()
2945 while threads:
2946 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002947
2948
Dan Albert8b72aef2015-03-23 19:13:21 -07002949class BlockDifference(object):
2950 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002951 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002952 self.tgt = tgt
2953 self.src = src
2954 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002955 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002956 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002957
Tao Baodd2a5892015-03-12 12:32:37 -07002958 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002959 version = max(
2960 int(i) for i in
2961 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002962 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002963 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002964
Tianjie Xu41976c72019-07-03 13:57:01 -07002965 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2966 version=self.version,
2967 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002968 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002969 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002970 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002971 self.touched_src_ranges = b.touched_src_ranges
2972 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002973
Yifan Hong10c530d2018-12-27 17:34:18 -08002974 # On devices with dynamic partitions, for new partitions,
2975 # src is None but OPTIONS.source_info_dict is not.
2976 if OPTIONS.source_info_dict is None:
2977 is_dynamic_build = OPTIONS.info_dict.get(
2978 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002979 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002980 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002981 is_dynamic_build = OPTIONS.source_info_dict.get(
2982 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002983 is_dynamic_source = partition in shlex.split(
2984 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002985
Yifan Hongbb2658d2019-01-25 12:30:58 -08002986 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002987 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2988
Yifan Hongbb2658d2019-01-25 12:30:58 -08002989 # For dynamic partitions builds, check partition list in both source
2990 # and target build because new partitions may be added, and existing
2991 # partitions may be removed.
2992 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2993
Yifan Hong10c530d2018-12-27 17:34:18 -08002994 if is_dynamic:
2995 self.device = 'map_partition("%s")' % partition
2996 else:
2997 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002998 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2999 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003000 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003001 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3002 OPTIONS.source_info_dict)
3003 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003004
Tao Baod8d14be2016-02-04 14:26:02 -08003005 @property
3006 def required_cache(self):
3007 return self._required_cache
3008
Tao Bao76def242017-11-21 09:25:31 -08003009 def WriteScript(self, script, output_zip, progress=None,
3010 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003011 if not self.src:
3012 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003013 script.Print("Patching %s image unconditionally..." % (self.partition,))
3014 else:
3015 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003016
Dan Albert8b72aef2015-03-23 19:13:21 -07003017 if progress:
3018 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003019 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003020
3021 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003022 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003023
Tao Bao9bc6bb22015-11-09 16:58:28 -08003024 def WriteStrictVerifyScript(self, script):
3025 """Verify all the blocks in the care_map, including clobbered blocks.
3026
3027 This differs from the WriteVerifyScript() function: a) it prints different
3028 error messages; b) it doesn't allow half-way updated images to pass the
3029 verification."""
3030
3031 partition = self.partition
3032 script.Print("Verifying %s..." % (partition,))
3033 ranges = self.tgt.care_map
3034 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003035 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003036 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3037 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003038 self.device, ranges_str,
3039 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003040 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003041 script.AppendExtra("")
3042
Tao Baod522bdc2016-04-12 15:53:16 -07003043 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003044 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003045
3046 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003047 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003048 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003049
3050 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003051 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003052 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003053 ranges = self.touched_src_ranges
3054 expected_sha1 = self.touched_src_sha1
3055 else:
3056 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3057 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003058
3059 # No blocks to be checked, skipping.
3060 if not ranges:
3061 return
3062
Tao Bao5ece99d2015-05-12 11:42:31 -07003063 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003064 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003065 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003066 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3067 '"%s.patch.dat")) then' % (
3068 self.device, ranges_str, expected_sha1,
3069 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003070 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003071 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003072
Tianjie Xufc3422a2015-12-15 11:53:59 -08003073 if self.version >= 4:
3074
3075 # Bug: 21124327
3076 # When generating incrementals for the system and vendor partitions in
3077 # version 4 or newer, explicitly check the first block (which contains
3078 # the superblock) of the partition to see if it's what we expect. If
3079 # this check fails, give an explicit log message about the partition
3080 # having been remounted R/W (the most likely explanation).
3081 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003082 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003083
3084 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003085 if partition == "system":
3086 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3087 else:
3088 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003089 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003090 'ifelse (block_image_recover({device}, "{ranges}") && '
3091 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003092 'package_extract_file("{partition}.transfer.list"), '
3093 '"{partition}.new.dat", "{partition}.patch.dat"), '
3094 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003095 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003096 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003097 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003098
Tao Baodd2a5892015-03-12 12:32:37 -07003099 # Abort the OTA update. Note that the incremental OTA cannot be applied
3100 # even if it may match the checksum of the target partition.
3101 # a) If version < 3, operations like move and erase will make changes
3102 # unconditionally and damage the partition.
3103 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003104 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003105 if partition == "system":
3106 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3107 else:
3108 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3109 script.AppendExtra((
3110 'abort("E%d: %s partition has unexpected contents");\n'
3111 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003112
Yifan Hong10c530d2018-12-27 17:34:18 -08003113 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003114 partition = self.partition
3115 script.Print('Verifying the updated %s image...' % (partition,))
3116 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3117 ranges = self.tgt.care_map
3118 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003119 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003120 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003121 self.device, ranges_str,
3122 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003123
3124 # Bug: 20881595
3125 # Verify that extended blocks are really zeroed out.
3126 if self.tgt.extended:
3127 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003128 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003129 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003130 self.device, ranges_str,
3131 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003132 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003133 if partition == "system":
3134 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3135 else:
3136 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003137 script.AppendExtra(
3138 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003139 ' abort("E%d: %s partition has unexpected non-zero contents after '
3140 'OTA update");\n'
3141 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003142 else:
3143 script.Print('Verified the updated %s image.' % (partition,))
3144
Tianjie Xu209db462016-05-24 17:34:52 -07003145 if partition == "system":
3146 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3147 else:
3148 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3149
Tao Bao5fcaaef2015-06-01 13:40:49 -07003150 script.AppendExtra(
3151 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003152 ' abort("E%d: %s partition has unexpected contents after OTA '
3153 'update");\n'
3154 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003155
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003156 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003157 ZipWrite(output_zip,
3158 '{}.transfer.list'.format(self.path),
3159 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003160
Tao Bao76def242017-11-21 09:25:31 -08003161 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3162 # its size. Quailty 9 almost triples the compression time but doesn't
3163 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003164 # zip | brotli(quality 6) | brotli(quality 9)
3165 # compressed_size: 942M | 869M (~8% reduced) | 854M
3166 # compression_time: 75s | 265s | 719s
3167 # decompression_time: 15s | 25s | 25s
3168
3169 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003170 brotli_cmd = ['brotli', '--quality=6',
3171 '--output={}.new.dat.br'.format(self.path),
3172 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003173 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003174 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003175
3176 new_data_name = '{}.new.dat.br'.format(self.partition)
3177 ZipWrite(output_zip,
3178 '{}.new.dat.br'.format(self.path),
3179 new_data_name,
3180 compress_type=zipfile.ZIP_STORED)
3181 else:
3182 new_data_name = '{}.new.dat'.format(self.partition)
3183 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3184
Dan Albert8e0178d2015-01-27 15:53:15 -08003185 ZipWrite(output_zip,
3186 '{}.patch.dat'.format(self.path),
3187 '{}.patch.dat'.format(self.partition),
3188 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003189
Tianjie Xu209db462016-05-24 17:34:52 -07003190 if self.partition == "system":
3191 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3192 else:
3193 code = ErrorCode.VENDOR_UPDATE_FAILURE
3194
Yifan Hong10c530d2018-12-27 17:34:18 -08003195 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003196 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003197 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003198 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003199 device=self.device, partition=self.partition,
3200 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003201 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003202
Kelvin Zhang0876c412020-06-23 15:06:58 -04003203 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003204 data = source.ReadRangeSet(ranges)
3205 ctx = sha1()
3206
3207 for p in data:
3208 ctx.update(p)
3209
3210 return ctx.hexdigest()
3211
Kelvin Zhang0876c412020-06-23 15:06:58 -04003212 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003213 """Return the hash value for all zero blocks."""
3214 zero_block = '\x00' * 4096
3215 ctx = sha1()
3216 for _ in range(num_blocks):
3217 ctx.update(zero_block)
3218
3219 return ctx.hexdigest()
3220
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003221
Tianjie Xu41976c72019-07-03 13:57:01 -07003222# Expose these two classes to support vendor-specific scripts
3223DataImage = images.DataImage
3224EmptyImage = images.EmptyImage
3225
Tao Bao76def242017-11-21 09:25:31 -08003226
Doug Zongker96a57e72010-09-26 14:57:41 -07003227# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003228PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003229 "ext4": "EMMC",
3230 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003231 "f2fs": "EMMC",
3232 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003233}
Doug Zongker96a57e72010-09-26 14:57:41 -07003234
Kelvin Zhang0876c412020-06-23 15:06:58 -04003235
Yifan Hongbdb32012020-05-07 12:38:53 -07003236def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3237 """
3238 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3239 backwards compatibility. It aborts if the fstab entry has slotselect option
3240 (unless check_no_slot is explicitly set to False).
3241 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003242 fstab = info["fstab"]
3243 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003244 if check_no_slot:
3245 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003246 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003247 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3248 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003249 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003250
3251
Yifan Hongbdb32012020-05-07 12:38:53 -07003252def GetTypeAndDeviceExpr(mount_point, info):
3253 """
3254 Return the filesystem of the partition, and an edify expression that evaluates
3255 to the device at runtime.
3256 """
3257 fstab = info["fstab"]
3258 if fstab:
3259 p = fstab[mount_point]
3260 device_expr = '"%s"' % fstab[mount_point].device
3261 if p.slotselect:
3262 device_expr = 'add_slot_suffix(%s)' % device_expr
3263 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003264 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003265
3266
3267def GetEntryForDevice(fstab, device):
3268 """
3269 Returns:
3270 The first entry in fstab whose device is the given value.
3271 """
3272 if not fstab:
3273 return None
3274 for mount_point in fstab:
3275 if fstab[mount_point].device == device:
3276 return fstab[mount_point]
3277 return None
3278
Kelvin Zhang0876c412020-06-23 15:06:58 -04003279
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003280def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003281 """Parses and converts a PEM-encoded certificate into DER-encoded.
3282
3283 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3284
3285 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003286 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003287 """
3288 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003289 save = False
3290 for line in data.split("\n"):
3291 if "--END CERTIFICATE--" in line:
3292 break
3293 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003294 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003295 if "--BEGIN CERTIFICATE--" in line:
3296 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003297 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003298 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003299
Tao Bao04e1f012018-02-04 12:13:35 -08003300
3301def ExtractPublicKey(cert):
3302 """Extracts the public key (PEM-encoded) from the given certificate file.
3303
3304 Args:
3305 cert: The certificate filename.
3306
3307 Returns:
3308 The public key string.
3309
3310 Raises:
3311 AssertionError: On non-zero return from 'openssl'.
3312 """
3313 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3314 # While openssl 1.1 writes the key into the given filename followed by '-out',
3315 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3316 # stdout instead.
3317 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3318 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3319 pubkey, stderrdata = proc.communicate()
3320 assert proc.returncode == 0, \
3321 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3322 return pubkey
3323
3324
Tao Bao1ac886e2019-06-26 11:58:22 -07003325def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003326 """Extracts the AVB public key from the given public or private key.
3327
3328 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003329 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003330 key: The input key file, which should be PEM-encoded public or private key.
3331
3332 Returns:
3333 The path to the extracted AVB public key file.
3334 """
3335 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3336 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003337 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003338 return output
3339
3340
Doug Zongker412c02f2014-02-13 10:58:24 -08003341def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3342 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003343 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003344
Tao Bao6d5d6232018-03-09 17:04:42 -08003345 Most of the space in the boot and recovery images is just the kernel, which is
3346 identical for the two, so the resulting patch should be efficient. Add it to
3347 the output zip, along with a shell script that is run from init.rc on first
3348 boot to actually do the patching and install the new recovery image.
3349
3350 Args:
3351 input_dir: The top-level input directory of the target-files.zip.
3352 output_sink: The callback function that writes the result.
3353 recovery_img: File object for the recovery image.
3354 boot_img: File objects for the boot image.
3355 info_dict: A dict returned by common.LoadInfoDict() on the input
3356 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003357 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003358 if info_dict is None:
3359 info_dict = OPTIONS.info_dict
3360
Tao Bao6d5d6232018-03-09 17:04:42 -08003361 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003362 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3363
3364 if board_uses_vendorimage:
3365 # In this case, the output sink is rooted at VENDOR
3366 recovery_img_path = "etc/recovery.img"
3367 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3368 sh_dir = "bin"
3369 else:
3370 # In this case the output sink is rooted at SYSTEM
3371 recovery_img_path = "vendor/etc/recovery.img"
3372 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3373 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003374
Tao Baof2cffbd2015-07-22 12:33:18 -07003375 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003376 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003377
3378 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003379 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003380 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003381 # With system-root-image, boot and recovery images will have mismatching
3382 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3383 # to handle such a case.
3384 if system_root_image:
3385 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003386 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003387 assert not os.path.exists(path)
3388 else:
3389 diff_program = ["imgdiff"]
3390 if os.path.exists(path):
3391 diff_program.append("-b")
3392 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003393 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003394 else:
3395 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003396
3397 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3398 _, _, patch = d.ComputePatch()
3399 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003400
Dan Albertebb19aa2015-03-27 19:11:53 -07003401 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003402 # The following GetTypeAndDevice()s need to use the path in the target
3403 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003404 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3405 check_no_slot=False)
3406 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3407 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003408 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003409 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003410
Tao Baof2cffbd2015-07-22 12:33:18 -07003411 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003412
3413 # Note that we use /vendor to refer to the recovery resources. This will
3414 # work for a separate vendor partition mounted at /vendor or a
3415 # /system/vendor subdirectory on the system partition, for which init will
3416 # create a symlink from /vendor to /system/vendor.
3417
3418 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003419if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3420 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003421 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003422 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3423 log -t recovery "Installing new recovery image: succeeded" || \\
3424 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003425else
3426 log -t recovery "Recovery image already installed"
3427fi
3428""" % {'type': recovery_type,
3429 'device': recovery_device,
3430 'sha1': recovery_img.sha1,
3431 'size': recovery_img.size}
3432 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003433 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003434if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3435 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003436 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003437 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3438 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3439 log -t recovery "Installing new recovery image: succeeded" || \\
3440 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003441else
3442 log -t recovery "Recovery image already installed"
3443fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003444""" % {'boot_size': boot_img.size,
3445 'boot_sha1': boot_img.sha1,
3446 'recovery_size': recovery_img.size,
3447 'recovery_sha1': recovery_img.sha1,
3448 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003449 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003450 'recovery_type': recovery_type,
3451 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003452 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003453
Bill Peckhame868aec2019-09-17 17:06:47 -07003454 # The install script location moved from /system/etc to /system/bin in the L
3455 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3456 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003457
Tao Bao32fcdab2018-10-12 10:30:39 -07003458 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003459
Tao Baoda30cfa2017-12-01 16:19:46 -08003460 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003461
3462
3463class DynamicPartitionUpdate(object):
3464 def __init__(self, src_group=None, tgt_group=None, progress=None,
3465 block_difference=None):
3466 self.src_group = src_group
3467 self.tgt_group = tgt_group
3468 self.progress = progress
3469 self.block_difference = block_difference
3470
3471 @property
3472 def src_size(self):
3473 if not self.block_difference:
3474 return 0
3475 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3476
3477 @property
3478 def tgt_size(self):
3479 if not self.block_difference:
3480 return 0
3481 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3482
3483 @staticmethod
3484 def _GetSparseImageSize(img):
3485 if not img:
3486 return 0
3487 return img.blocksize * img.total_blocks
3488
3489
3490class DynamicGroupUpdate(object):
3491 def __init__(self, src_size=None, tgt_size=None):
3492 # None: group does not exist. 0: no size limits.
3493 self.src_size = src_size
3494 self.tgt_size = tgt_size
3495
3496
3497class DynamicPartitionsDifference(object):
3498 def __init__(self, info_dict, block_diffs, progress_dict=None,
3499 source_info_dict=None):
3500 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003501 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003502
3503 self._remove_all_before_apply = False
3504 if source_info_dict is None:
3505 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003506 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003507
Tao Baof1113e92019-06-18 12:10:14 -07003508 block_diff_dict = collections.OrderedDict(
3509 [(e.partition, e) for e in block_diffs])
3510
Yifan Hong10c530d2018-12-27 17:34:18 -08003511 assert len(block_diff_dict) == len(block_diffs), \
3512 "Duplicated BlockDifference object for {}".format(
3513 [partition for partition, count in
3514 collections.Counter(e.partition for e in block_diffs).items()
3515 if count > 1])
3516
Yifan Hong79997e52019-01-23 16:56:19 -08003517 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003518
3519 for p, block_diff in block_diff_dict.items():
3520 self._partition_updates[p] = DynamicPartitionUpdate()
3521 self._partition_updates[p].block_difference = block_diff
3522
3523 for p, progress in progress_dict.items():
3524 if p in self._partition_updates:
3525 self._partition_updates[p].progress = progress
3526
3527 tgt_groups = shlex.split(info_dict.get(
3528 "super_partition_groups", "").strip())
3529 src_groups = shlex.split(source_info_dict.get(
3530 "super_partition_groups", "").strip())
3531
3532 for g in tgt_groups:
3533 for p in shlex.split(info_dict.get(
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003534 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003535 assert p in self._partition_updates, \
3536 "{} is in target super_{}_partition_list but no BlockDifference " \
3537 "object is provided.".format(p, g)
3538 self._partition_updates[p].tgt_group = g
3539
3540 for g in src_groups:
3541 for p in shlex.split(source_info_dict.get(
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003542 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003543 assert p in self._partition_updates, \
3544 "{} is in source super_{}_partition_list but no BlockDifference " \
3545 "object is provided.".format(p, g)
3546 self._partition_updates[p].src_group = g
3547
Yifan Hong45433e42019-01-18 13:55:25 -08003548 target_dynamic_partitions = set(shlex.split(info_dict.get(
3549 "dynamic_partition_list", "").strip()))
3550 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3551 if u.tgt_size)
3552 assert block_diffs_with_target == target_dynamic_partitions, \
3553 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3554 list(target_dynamic_partitions), list(block_diffs_with_target))
3555
3556 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3557 "dynamic_partition_list", "").strip()))
3558 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3559 if u.src_size)
3560 assert block_diffs_with_source == source_dynamic_partitions, \
3561 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3562 list(source_dynamic_partitions), list(block_diffs_with_source))
3563
Yifan Hong10c530d2018-12-27 17:34:18 -08003564 if self._partition_updates:
3565 logger.info("Updating dynamic partitions %s",
3566 self._partition_updates.keys())
3567
Yifan Hong79997e52019-01-23 16:56:19 -08003568 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003569
3570 for g in tgt_groups:
3571 self._group_updates[g] = DynamicGroupUpdate()
3572 self._group_updates[g].tgt_size = int(info_dict.get(
3573 "super_%s_group_size" % g, "0").strip())
3574
3575 for g in src_groups:
3576 if g not in self._group_updates:
3577 self._group_updates[g] = DynamicGroupUpdate()
3578 self._group_updates[g].src_size = int(source_info_dict.get(
3579 "super_%s_group_size" % g, "0").strip())
3580
3581 self._Compute()
3582
3583 def WriteScript(self, script, output_zip, write_verify_script=False):
3584 script.Comment('--- Start patching dynamic partitions ---')
3585 for p, u in self._partition_updates.items():
3586 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3587 script.Comment('Patch partition %s' % p)
3588 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3589 write_verify_script=False)
3590
3591 op_list_path = MakeTempFile()
3592 with open(op_list_path, 'w') as f:
3593 for line in self._op_list:
3594 f.write('{}\n'.format(line))
3595
3596 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3597
3598 script.Comment('Update dynamic partition metadata')
3599 script.AppendExtra('assert(update_dynamic_partitions('
3600 'package_extract_file("dynamic_partitions_op_list")));')
3601
3602 if write_verify_script:
3603 for p, u in self._partition_updates.items():
3604 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3605 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003606 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003607
3608 for p, u in self._partition_updates.items():
3609 if u.tgt_size and u.src_size <= u.tgt_size:
3610 script.Comment('Patch partition %s' % p)
3611 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3612 write_verify_script=write_verify_script)
3613 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003614 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003615
3616 script.Comment('--- End patching dynamic partitions ---')
3617
3618 def _Compute(self):
3619 self._op_list = list()
3620
3621 def append(line):
3622 self._op_list.append(line)
3623
3624 def comment(line):
3625 self._op_list.append("# %s" % line)
3626
3627 if self._remove_all_before_apply:
3628 comment('Remove all existing dynamic partitions and groups before '
3629 'applying full OTA')
3630 append('remove_all_groups')
3631
3632 for p, u in self._partition_updates.items():
3633 if u.src_group and not u.tgt_group:
3634 append('remove %s' % p)
3635
3636 for p, u in self._partition_updates.items():
3637 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3638 comment('Move partition %s from %s to default' % (p, u.src_group))
3639 append('move %s default' % p)
3640
3641 for p, u in self._partition_updates.items():
3642 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3643 comment('Shrink partition %s from %d to %d' %
3644 (p, u.src_size, u.tgt_size))
3645 append('resize %s %s' % (p, u.tgt_size))
3646
3647 for g, u in self._group_updates.items():
3648 if u.src_size is not None and u.tgt_size is None:
3649 append('remove_group %s' % g)
3650 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003651 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003652 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3653 append('resize_group %s %d' % (g, u.tgt_size))
3654
3655 for g, u in self._group_updates.items():
3656 if u.src_size is None and u.tgt_size is not None:
3657 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3658 append('add_group %s %d' % (g, u.tgt_size))
3659 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003660 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003661 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3662 append('resize_group %s %d' % (g, u.tgt_size))
3663
3664 for p, u in self._partition_updates.items():
3665 if u.tgt_group and not u.src_group:
3666 comment('Add partition %s to group %s' % (p, u.tgt_group))
3667 append('add %s %s' % (p, u.tgt_group))
3668
3669 for p, u in self._partition_updates.items():
3670 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003671 comment('Grow partition %s from %d to %d' %
3672 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003673 append('resize %s %d' % (p, u.tgt_size))
3674
3675 for p, u in self._partition_updates.items():
3676 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3677 comment('Move partition %s from default to %s' %
3678 (p, u.tgt_group))
3679 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003680
3681
jiajia tangf3f842b2021-03-17 21:49:44 +08003682def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003683 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003684 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003685
3686 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003687 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003688
3689 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003690 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003691 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003692 tmp_dir = MakeTempDir('boot_', suffix='.img')
3693 try:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003694 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3695 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003696 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3697 if not os.path.isfile(ramdisk):
3698 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3699 return None
3700 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003701 if ramdisk_format == RamdiskFormat.LZ4:
3702 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3703 elif ramdisk_format == RamdiskFormat.GZ:
3704 with open(ramdisk, 'rb') as input_stream:
3705 with open(uncompressed_ramdisk, 'wb') as output_stream:
3706 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(), stdout=output_stream.fileno())
3707 p2.wait()
3708 else:
3709 logger.error('Only support lz4 or minigzip ramdisk format.')
3710 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003711
3712 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3713 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3714 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3715 # the host environment.
3716 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003717 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003718
Yifan Hongc65a0542021-01-07 14:21:01 -08003719 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3720 prop_file = os.path.join(extracted_ramdisk, search_path)
3721 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003722 return prop_file
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003723 logger.warning(
3724 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003725
Yifan Hong7dc51172021-01-12 11:27:39 -08003726 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003727
Yifan Hong85ac5012021-01-07 14:43:46 -08003728 except ExternalError as e:
3729 logger.warning('Unable to get boot image build props: %s', e)
3730 return None
3731
3732
3733def GetBootImageTimestamp(boot_img):
3734 """
3735 Get timestamp from ramdisk within the boot image
3736
3737 Args:
3738 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3739
3740 Return:
3741 An integer that corresponds to the timestamp of the boot image, or None
3742 if file has unknown format. Raise exception if an unexpected error has
3743 occurred.
3744 """
3745 prop_file = GetBootImageBuildProp(boot_img)
3746 if not prop_file:
3747 return None
3748
3749 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3750 if props is None:
3751 return None
3752
3753 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003754 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3755 if timestamp:
3756 return int(timestamp)
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003757 logger.warning(
3758 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003759 return None
3760
3761 except ExternalError as e:
3762 logger.warning('Unable to get boot image timestamp: %s', e)
3763 return None
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003764
3765
3766def GetCareMap(which, imgname):
3767 """Returns the care_map string for the given partition.
3768
3769 Args:
3770 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3771 imgname: The filename of the image.
3772
3773 Returns:
3774 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3775 RangeSet; or None.
3776 """
3777 assert which in PARTITIONS_WITH_CARE_MAP
3778
3779 # which + "_image_size" contains the size that the actual filesystem image
3780 # resides in, which is all that needs to be verified. The additional blocks in
3781 # the image file contain verity metadata, by reading which would trigger
3782 # invalid reads.
3783 image_size = OPTIONS.info_dict.get(which + "_image_size")
3784 if not image_size:
3785 return None
3786
3787 image_blocks = int(image_size) // 4096 - 1
3788 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3789
3790 # For sparse images, we will only check the blocks that are listed in the care
3791 # map, i.e. the ones with meaningful data.
3792 if "extfs_sparse_flag" in OPTIONS.info_dict:
3793 simg = sparse_img.SparseImage(imgname)
3794 care_map_ranges = simg.care_map.intersect(
3795 rangelib.RangeSet("0-{}".format(image_blocks)))
3796
3797 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3798 # image.
3799 else:
3800 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3801
3802 return [which, care_map_ranges.to_string_raw()]
3803
3804
3805def AddCareMapForAbOta(output_zip, ab_partitions, image_paths):
3806 """Generates and adds care_map.pb for a/b partition that has care_map.
3807
3808 Args:
3809 output_zip: The output zip file (needs to be already open), or None to
3810 write care_map.pb to OPTIONS.input_tmp/.
3811 ab_partitions: The list of A/B partitions.
3812 image_paths: A map from the partition name to the image path.
3813 """
3814 care_map_list = []
3815 for partition in ab_partitions:
3816 partition = partition.strip()
3817 if partition not in PARTITIONS_WITH_CARE_MAP:
3818 continue
3819
3820 verity_block_device = "{}_verity_block_device".format(partition)
3821 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3822 if (verity_block_device in OPTIONS.info_dict or
3823 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3824 image_path = image_paths[partition]
3825 assert os.path.exists(image_path)
3826
3827 care_map = GetCareMap(partition, image_path)
3828 if not care_map:
3829 continue
3830 care_map_list += care_map
3831
3832 # adds fingerprint field to the care_map
3833 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3834 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3835 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3836 "ro.{}.build.thumbprint".format(partition)]
3837
3838 present_props = [x for x in prop_name_list if
3839 partition_props and partition_props.GetProp(x)]
3840 if not present_props:
3841 logger.warning(
3842 "fingerprint is not present for partition %s", partition)
3843 property_id, fingerprint = "unknown", "unknown"
3844 else:
3845 property_id = present_props[0]
3846 fingerprint = partition_props.GetProp(property_id)
3847 care_map_list += [property_id, fingerprint]
3848
3849 if not care_map_list:
3850 return
3851
3852 # Converts the list into proto buf message by calling care_map_generator; and
3853 # writes the result to a temp file.
3854 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3855 suffix=".txt")
3856 with open(temp_care_map_text, 'w') as text_file:
3857 text_file.write('\n'.join(care_map_list))
3858
3859 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3860 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3861 RunAndCheckOutput(care_map_gen_cmd)
3862
3863 care_map_path = "META/care_map.pb"
3864 if output_zip and care_map_path not in output_zip.namelist():
3865 ZipWrite(output_zip, temp_care_map, arcname=care_map_path)
3866 else:
3867 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3868 if output_zip:
3869 OPTIONS.replace_updated_files_list.append(care_map_path)