blob: 49ef84d8a8d38d7a4819816523dbd33389294980 [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
Kelvin Zhang5f148302023-06-21 13:06:59 -070037import stat
Doug Zongkereef39442009-04-02 12:14:19 -070038import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070039import threading
40import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070041import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080042from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070043
Tianjie Xu41976c72019-07-03 13:57:01 -070044import images
Kelvin Zhang27324132021-03-22 15:38:38 -040045import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080046import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070047from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070048
Tao Bao32fcdab2018-10-12 10:30:39 -070049logger = logging.getLogger(__name__)
50
Tao Bao986ee862018-10-04 15:46:16 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070053
Dan Albert8b72aef2015-03-23 19:13:21 -070054 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070055 # Set up search path, in order to find framework/ and lib64/. At the time of
56 # running this function, user-supplied search path (`--path`) hasn't been
57 # available. So the value set here is the default, which might be overridden
58 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040059 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070060 if exec_path.endswith('.py'):
61 script_name = os.path.basename(exec_path)
62 # logger hasn't been initialized yet at this point. Use print to output
63 # warnings.
64 print(
65 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040066 'executable -- build and run `{}` directly.'.format(
67 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070068 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040069 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030070
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080072 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
73 if "ANDROID_HOST_OUT" in os.environ:
74 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080075 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070076 self.extra_signapk_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000077 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.java_path = "java" # Use the one on the path by default.
Sorin Basca64f17c12022-09-14 11:33:22 +010079 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080080 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070081 self.public_key_suffix = ".x509.pem"
82 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070083 # use otatools built boot_signer by default
84 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070085 self.boot_signer_args = []
86 self.verity_signer_path = None
87 self.verity_signer_args = []
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.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000116AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Lucas Wei03230252022-04-18 16:00:40 +0800117 'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000118 'vendor_dlkm', 'odm_dlkm', 'system_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',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000132 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400133]
Tianjie Xu861f4132018-09-12 11:49:33 -0700134
Yifan Hong5057b952021-01-07 14:09:57 -0800135# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000136PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800137
Yifan Hongc65a0542021-01-07 14:21:01 -0800138# See sysprop.mk. If file is moved, add new search paths here; don't remove
139# existing search paths.
140RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700141
Kelvin Zhang563750f2021-04-28 12:46:17 -0400142
Tianjie Xu209db462016-05-24 17:34:52 -0700143class ErrorCode(object):
144 """Define error_codes for failures that happen during the actual
145 update package installation.
146
147 Error codes 0-999 are reserved for failures before the package
148 installation (i.e. low battery, package verification failure).
149 Detailed code in 'bootable/recovery/error_code.h' """
150
151 SYSTEM_VERIFICATION_FAILURE = 1000
152 SYSTEM_UPDATE_FAILURE = 1001
153 SYSTEM_UNEXPECTED_CONTENTS = 1002
154 SYSTEM_NONZERO_CONTENTS = 1003
155 SYSTEM_RECOVER_FAILURE = 1004
156 VENDOR_VERIFICATION_FAILURE = 2000
157 VENDOR_UPDATE_FAILURE = 2001
158 VENDOR_UNEXPECTED_CONTENTS = 2002
159 VENDOR_NONZERO_CONTENTS = 2003
160 VENDOR_RECOVER_FAILURE = 2004
161 OEM_PROP_MISMATCH = 3000
162 FINGERPRINT_MISMATCH = 3001
163 THUMBPRINT_MISMATCH = 3002
164 OLDER_BUILD = 3003
165 DEVICE_MISMATCH = 3004
166 BAD_PATCH_FILE = 3005
167 INSUFFICIENT_CACHE_SPACE = 3006
168 TUNE_PARTITION_FAILURE = 3007
169 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800170
Tao Bao80921982018-03-21 21:02:19 -0700171
Dan Albert8b72aef2015-03-23 19:13:21 -0700172class ExternalError(RuntimeError):
173 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700174
175
Tao Bao32fcdab2018-10-12 10:30:39 -0700176def InitLogging():
177 DEFAULT_LOGGING_CONFIG = {
178 'version': 1,
179 'disable_existing_loggers': False,
180 'formatters': {
181 'standard': {
182 'format':
183 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
184 'datefmt': '%Y-%m-%d %H:%M:%S',
185 },
186 },
187 'handlers': {
188 'default': {
189 'class': 'logging.StreamHandler',
190 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700191 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 },
193 },
194 'loggers': {
195 '': {
196 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700198 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700199 }
200 }
201 }
202 env_config = os.getenv('LOGGING_CONFIG')
203 if env_config:
204 with open(env_config) as f:
205 config = json.load(f)
206 else:
207 config = DEFAULT_LOGGING_CONFIG
208
209 # Increase the logging level for verbose mode.
210 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700211 config = copy.deepcopy(config)
212 config['handlers']['default']['level'] = 'INFO'
213
214 if OPTIONS.logfile:
215 config = copy.deepcopy(config)
216 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400217 'class': 'logging.FileHandler',
218 'formatter': 'standard',
219 'level': 'INFO',
220 'mode': 'w',
221 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700222 }
223 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700224
225 logging.config.dictConfig(config)
226
227
Yifan Hong8e332ff2020-07-29 17:51:55 -0700228def SetHostToolLocation(tool_name, location):
229 OPTIONS.host_tools[tool_name] = location
230
Kelvin Zhang563750f2021-04-28 12:46:17 -0400231
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900232def FindHostToolPath(tool_name):
233 """Finds the path to the host tool.
234
235 Args:
236 tool_name: name of the tool to find
237 Returns:
238 path to the tool if found under either one of the host_tools map or under
239 the same directory as this binary is located at. If not found, tool_name
240 is returned.
241 """
242 if tool_name in OPTIONS.host_tools:
243 return OPTIONS.host_tools[tool_name]
244
245 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
246 tool_path = os.path.join(my_dir, tool_name)
247 if os.path.exists(tool_path):
248 return tool_path
249
250 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700251
Kelvin Zhang563750f2021-04-28 12:46:17 -0400252
Tao Bao39451582017-05-04 11:10:47 -0700253def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700254 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700255
Tao Bao73dd4f42018-10-04 16:25:33 -0700256 Args:
257 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700258 verbose: Whether the commands should be shown. Default to the global
259 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700260 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
261 stdin, etc. stdout and stderr will default to subprocess.PIPE and
262 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800263 universal_newlines will default to True, as most of the users in
264 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700265
266 Returns:
267 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700268 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700269 if 'stdout' not in kwargs and 'stderr' not in kwargs:
270 kwargs['stdout'] = subprocess.PIPE
271 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800272 if 'universal_newlines' not in kwargs:
273 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700274
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900275 if args:
276 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700277 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900278 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700279
Kelvin Zhang766eea72021-06-03 09:36:08 -0400280 if verbose is None:
281 verbose = OPTIONS.verbose
282
Tao Bao32fcdab2018-10-12 10:30:39 -0700283 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400284 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700285 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700286 return subprocess.Popen(args, **kwargs)
287
288
Tao Bao986ee862018-10-04 15:46:16 -0700289def RunAndCheckOutput(args, verbose=None, **kwargs):
290 """Runs the given command and returns the output.
291
292 Args:
293 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700294 verbose: Whether the commands should be shown. Default to the global
295 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700296 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
297 stdin, etc. stdout and stderr will default to subprocess.PIPE and
298 subprocess.STDOUT respectively unless caller specifies any of them.
299
300 Returns:
301 The output string.
302
303 Raises:
304 ExternalError: On non-zero exit from the command.
305 """
Tao Bao986ee862018-10-04 15:46:16 -0700306 proc = Run(args, verbose=verbose, **kwargs)
307 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800308 if output is None:
309 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700310 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400311 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700312 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700313 if proc.returncode != 0:
314 raise ExternalError(
315 "Failed to run command '{}' (exit code {}):\n{}".format(
316 args, proc.returncode, output))
317 return output
318
319
Tao Baoc765cca2018-01-31 17:32:40 -0800320def RoundUpTo4K(value):
321 rounded_up = value + 4095
322 return rounded_up - (rounded_up % 4096)
323
324
Ying Wang7e6d4e42010-12-13 16:25:36 -0800325def CloseInheritedPipes():
326 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
327 before doing other work."""
328 if platform.system() != "Darwin":
329 return
330 for d in range(3, 1025):
331 try:
332 stat = os.fstat(d)
333 if stat is not None:
334 pipebit = stat[0] & 0x1000
335 if pipebit != 0:
336 os.close(d)
337 except OSError:
338 pass
339
340
Tao Bao1c320f82019-10-04 23:25:12 -0700341class BuildInfo(object):
342 """A class that holds the information for a given build.
343
344 This class wraps up the property querying for a given source or target build.
345 It abstracts away the logic of handling OEM-specific properties, and caches
346 the commonly used properties such as fingerprint.
347
348 There are two types of info dicts: a) build-time info dict, which is generated
349 at build time (i.e. included in a target_files zip); b) OEM info dict that is
350 specified at package generation time (via command line argument
351 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
352 having "oem_fingerprint_properties" in build-time info dict), all the queries
353 would be answered based on build-time info dict only. Otherwise if using
354 OEM-specific properties, some of them will be calculated from two info dicts.
355
356 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800357 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700358
359 Attributes:
360 info_dict: The build-time info dict.
361 is_ab: Whether it's a build that uses A/B OTA.
362 oem_dicts: A list of OEM dicts.
363 oem_props: A list of OEM properties that should be read from OEM dicts; None
364 if the build doesn't use any OEM-specific property.
365 fingerprint: The fingerprint of the build, which would be calculated based
366 on OEM properties if applicable.
367 device: The device name, which could come from OEM dicts if applicable.
368 """
369
370 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
371 "ro.product.manufacturer", "ro.product.model",
Alexander Martinz2f52b052022-04-08 10:56:54 +0200372 "ro.product.name",
373 "ro.shift.sos.version.number",
374 "ro.shift.sos.version.extra",
375 "ro.shift.release.type"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700376 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
377 "product", "odm", "vendor", "system_ext", "system"]
378 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
379 "product", "product_services", "odm", "vendor", "system"]
380 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700381
Tianjiefdda51d2021-05-05 14:46:35 -0700382 # The length of vbmeta digest to append to the fingerprint
383 _VBMETA_DIGEST_SIZE_USED = 8
384
385 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700386 """Initializes a BuildInfo instance with the given dicts.
387
388 Note that it only wraps up the given dicts, without making copies.
389
390 Arguments:
391 info_dict: The build-time info dict.
392 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
393 that it always uses the first dict to calculate the fingerprint or the
394 device name. The rest would be used for asserting OEM properties only
395 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700396 use_legacy_id: Use the legacy build id to construct the fingerprint. This
397 is used when we need a BuildInfo class, while the vbmeta digest is
398 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700399
400 Raises:
401 ValueError: On invalid inputs.
402 """
403 self.info_dict = info_dict
404 self.oem_dicts = oem_dicts
405
406 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700407 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700408
Hongguang Chend7c160f2020-05-03 21:24:26 -0700409 # Skip _oem_props if oem_dicts is None to use BuildInfo in
410 # sign_target_files_apks
411 if self.oem_dicts:
412 self._oem_props = info_dict.get("oem_fingerprint_properties")
413 else:
414 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700415
Daniel Normand5fe8622020-01-08 17:01:11 -0800416 def check_fingerprint(fingerprint):
417 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
418 raise ValueError(
419 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
420 "3.2.2. Build Parameters.".format(fingerprint))
421
Daniel Normand5fe8622020-01-08 17:01:11 -0800422 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800423 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800424 try:
425 fingerprint = self.CalculatePartitionFingerprint(partition)
426 check_fingerprint(fingerprint)
427 self._partition_fingerprints[partition] = fingerprint
428 except ExternalError:
429 continue
430 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800431 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800432 # need a fingerprint when creating the image.
433 self._partition_fingerprints[
434 "system_other"] = self._partition_fingerprints["system"]
435
Tao Bao1c320f82019-10-04 23:25:12 -0700436 # These two should be computed only after setting self._oem_props.
Steve Kondik4b8a85a2010-04-21 11:39:48 -0400437 self._device = info_dict.get("ota_override_device", self.GetOemProperty("ro.product.device"))
Tao Bao1c320f82019-10-04 23:25:12 -0700438 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800439 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700440
441 @property
442 def is_ab(self):
443 return self._is_ab
444
445 @property
446 def device(self):
447 return self._device
448
449 @property
450 def fingerprint(self):
451 return self._fingerprint
452
453 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400454 def is_vabc(self):
455 vendor_prop = self.info_dict.get("vendor.build.prop")
456 vabc_enabled = vendor_prop and \
457 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
458 return vabc_enabled
459
460 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700461 def is_vabc_xor(self):
462 vendor_prop = self.info_dict.get("vendor.build.prop")
463 vabc_xor_enabled = vendor_prop and \
464 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
465 return vabc_xor_enabled
466
467 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400468 def vendor_suppressed_vabc(self):
469 vendor_prop = self.info_dict.get("vendor.build.prop")
470 vabc_suppressed = vendor_prop and \
471 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
472 return vabc_suppressed and vabc_suppressed.lower() == "true"
473
474 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700475 def oem_props(self):
476 return self._oem_props
477
478 def __getitem__(self, key):
479 return self.info_dict[key]
480
481 def __setitem__(self, key, value):
482 self.info_dict[key] = value
483
484 def get(self, key, default=None):
485 return self.info_dict.get(key, default)
486
487 def items(self):
488 return self.info_dict.items()
489
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000490 def _GetRawBuildProp(self, prop, partition):
491 prop_file = '{}.build.prop'.format(
492 partition) if partition else 'build.prop'
493 partition_props = self.info_dict.get(prop_file)
494 if not partition_props:
495 return None
496 return partition_props.GetProp(prop)
497
Daniel Normand5fe8622020-01-08 17:01:11 -0800498 def GetPartitionBuildProp(self, prop, partition):
499 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800500
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000501 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000502 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000503 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800504
Daniel Normand5fe8622020-01-08 17:01:11 -0800505 # If provided a partition for this property, only look within that
506 # partition's build.prop.
507 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800508 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800509 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800510 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000511
512 prop_val = self._GetRawBuildProp(prop, partition)
513 if prop_val is not None:
514 return prop_val
515 raise ExternalError("couldn't find %s in %s.build.prop" %
516 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800517
Tao Bao1c320f82019-10-04 23:25:12 -0700518 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800519 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700520 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
521 return self._ResolveRoProductBuildProp(prop)
522
Tianjiefdda51d2021-05-05 14:46:35 -0700523 if prop == "ro.build.id":
524 return self._GetBuildId()
525
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000526 prop_val = self._GetRawBuildProp(prop, None)
527 if prop_val is not None:
528 return prop_val
529
530 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700531
532 def _ResolveRoProductBuildProp(self, prop):
533 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000534 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700535 if prop_val:
536 return prop_val
537
Steven Laver8e2086e2020-04-27 16:26:31 -0700538 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000539 source_order_val = self._GetRawBuildProp(
540 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700541 if source_order_val:
542 source_order = source_order_val.split(",")
543 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700544 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700545
546 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700547 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700548 raise ExternalError(
549 "Invalid ro.product.property_source_order '{}'".format(source_order))
550
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000551 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700552 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000553 "ro.product", "ro.product.{}".format(source_partition), 1)
554 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700555 if prop_val:
556 return prop_val
557
558 raise ExternalError("couldn't resolve {}".format(prop))
559
Steven Laver8e2086e2020-04-27 16:26:31 -0700560 def _GetRoProductPropsDefaultSourceOrder(self):
561 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
562 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000563 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700564 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000565 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700566 if android_version == "10":
567 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
568 # NOTE: float() conversion of android_version will have rounding error.
569 # We are checking for "9" or less, and using "< 10" is well outside of
570 # possible floating point rounding.
571 try:
572 android_version_val = float(android_version)
573 except ValueError:
574 android_version_val = 0
575 if android_version_val < 10:
576 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
577 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
578
Tianjieb37c5be2020-10-15 21:27:10 -0700579 def _GetPlatformVersion(self):
580 version_sdk = self.GetBuildProp("ro.build.version.sdk")
581 # init code switches to version_release_or_codename (see b/158483506). After
582 # API finalization, release_or_codename will be the same as release. This
583 # is the best effort to support pre-S dev stage builds.
584 if int(version_sdk) >= 30:
585 try:
586 return self.GetBuildProp("ro.build.version.release_or_codename")
587 except ExternalError:
588 logger.warning('Failed to find ro.build.version.release_or_codename')
589
590 return self.GetBuildProp("ro.build.version.release")
591
Tianjiefdda51d2021-05-05 14:46:35 -0700592 def _GetBuildId(self):
593 build_id = self._GetRawBuildProp("ro.build.id", None)
594 if build_id:
595 return build_id
596
597 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
598 if not legacy_build_id:
599 raise ExternalError("Couldn't find build id in property file")
600
601 if self.use_legacy_id:
602 return legacy_build_id
603
604 # Append the top 8 chars of vbmeta digest to the existing build id. The
605 # logic needs to match the one in init, so that OTA can deliver correctly.
606 avb_enable = self.info_dict.get("avb_enable") == "true"
607 if not avb_enable:
608 raise ExternalError("AVB isn't enabled when using legacy build id")
609
610 vbmeta_digest = self.info_dict.get("vbmeta_digest")
611 if not vbmeta_digest:
612 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
613 " id")
614 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
615 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
616
617 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
618 return legacy_build_id + '.' + digest_prefix
619
Tianjieb37c5be2020-10-15 21:27:10 -0700620 def _GetPartitionPlatformVersion(self, partition):
621 try:
622 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
623 partition)
624 except ExternalError:
625 return self.GetPartitionBuildProp("ro.build.version.release",
626 partition)
627
Tao Bao1c320f82019-10-04 23:25:12 -0700628 def GetOemProperty(self, key):
629 if self.oem_props is not None and key in self.oem_props:
630 return self.oem_dicts[0][key]
631 return self.GetBuildProp(key)
632
Daniel Normand5fe8622020-01-08 17:01:11 -0800633 def GetPartitionFingerprint(self, partition):
Aaron Kling088a3ef2021-02-17 14:10:32 -0600634 return self._partition_fingerprints.get(partition, self.CalculateFingerprint())
Daniel Normand5fe8622020-01-08 17:01:11 -0800635
636 def CalculatePartitionFingerprint(self, partition):
637 try:
638 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
639 except ExternalError:
640 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
641 self.GetPartitionBuildProp("ro.product.brand", partition),
642 self.GetPartitionBuildProp("ro.product.name", partition),
643 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700644 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800645 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400646 self.GetPartitionBuildProp(
647 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800648 self.GetPartitionBuildProp("ro.build.type", partition),
649 self.GetPartitionBuildProp("ro.build.tags", partition))
650
Tao Bao1c320f82019-10-04 23:25:12 -0700651 def CalculateFingerprint(self):
652 if self.oem_props is None:
653 try:
654 return self.GetBuildProp("ro.build.fingerprint")
655 except ExternalError:
656 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
657 self.GetBuildProp("ro.product.brand"),
658 self.GetBuildProp("ro.product.name"),
659 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700660 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700661 self.GetBuildProp("ro.build.id"),
662 self.GetBuildProp("ro.build.version.incremental"),
663 self.GetBuildProp("ro.build.type"),
664 self.GetBuildProp("ro.build.tags"))
665 return "%s/%s/%s:%s" % (
666 self.GetOemProperty("ro.product.brand"),
667 self.GetOemProperty("ro.product.name"),
668 self.GetOemProperty("ro.product.device"),
669 self.GetBuildProp("ro.build.thumbprint"))
670
671 def WriteMountOemScript(self, script):
672 assert self.oem_props is not None
673 recovery_mount_options = self.info_dict.get("recovery_mount_options")
674 script.Mount("/oem", recovery_mount_options)
675
676 def WriteDeviceAssertions(self, script, oem_no_mount):
677 # Read the property directly if not using OEM properties.
678 if not self.oem_props:
679 script.AssertDevice(self.device)
680 return
681
682 # Otherwise assert OEM properties.
683 if not self.oem_dicts:
684 raise ExternalError(
685 "No OEM file provided to answer expected assertions")
686
687 for prop in self.oem_props.split():
688 values = []
689 for oem_dict in self.oem_dicts:
690 if prop in oem_dict:
691 values.append(oem_dict[prop])
692 if not values:
693 raise ExternalError(
694 "The OEM file is missing the property %s" % (prop,))
695 script.AssertOemProperty(prop, values, oem_no_mount)
696
697
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000698def ReadFromInputFile(input_file, fn):
699 """Reads the contents of fn from input zipfile or directory."""
700 if isinstance(input_file, zipfile.ZipFile):
701 return input_file.read(fn).decode()
702 else:
703 path = os.path.join(input_file, *fn.split("/"))
704 try:
705 with open(path) as f:
706 return f.read()
707 except IOError as e:
708 if e.errno == errno.ENOENT:
709 raise KeyError(fn)
710
711
Yifan Hong10482a22021-01-07 14:38:41 -0800712def ExtractFromInputFile(input_file, fn):
713 """Extracts the contents of fn from input zipfile or directory into a file."""
714 if isinstance(input_file, zipfile.ZipFile):
715 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500716 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800717 f.write(input_file.read(fn))
718 return tmp_file
719 else:
720 file = os.path.join(input_file, *fn.split("/"))
721 if not os.path.exists(file):
722 raise KeyError(fn)
723 return file
724
Kelvin Zhang563750f2021-04-28 12:46:17 -0400725
jiajia tangf3f842b2021-03-17 21:49:44 +0800726class RamdiskFormat(object):
727 LZ4 = 1
728 GZ = 2
Luca Stefani7f2913c2020-06-11 13:03:18 +0200729 XZ = 3
Yifan Hong10482a22021-01-07 14:38:41 -0800730
Kelvin Zhang563750f2021-04-28 12:46:17 -0400731
jiajia tang836f76b2021-04-02 14:48:26 +0800732def _GetRamdiskFormat(info_dict):
733 if info_dict.get('lz4_ramdisks') == 'true':
734 ramdisk_format = RamdiskFormat.LZ4
pjgowtham7e3992a2022-11-18 02:43:17 +0530735 elif info_dict.get('xz_ramdisks') == 'true':
Luca Stefani7f2913c2020-06-11 13:03:18 +0200736 ramdisk_format = RamdiskFormat.XZ
jiajia tang836f76b2021-04-02 14:48:26 +0800737 else:
738 ramdisk_format = RamdiskFormat.GZ
739 return ramdisk_format
740
Kelvin Zhang563750f2021-04-28 12:46:17 -0400741
Tao Bao410ad8b2018-08-24 12:08:38 -0700742def LoadInfoDict(input_file, repacking=False):
743 """Loads the key/value pairs from the given input target_files.
744
Tianjiea85bdf02020-07-29 11:56:19 -0700745 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700746 checks and returns the parsed key/value pairs for to the given build. It's
747 usually called early when working on input target_files files, e.g. when
748 generating OTAs, or signing builds. Note that the function may be called
749 against an old target_files file (i.e. from past dessert releases). So the
750 property parsing needs to be backward compatible.
751
752 In a `META/misc_info.txt`, a few properties are stored as links to the files
753 in the PRODUCT_OUT directory. It works fine with the build system. However,
754 they are no longer available when (re)generating images from target_files zip.
755 When `repacking` is True, redirect these properties to the actual files in the
756 unzipped directory.
757
758 Args:
759 input_file: The input target_files file, which could be an open
760 zipfile.ZipFile instance, or a str for the dir that contains the files
761 unzipped from a target_files file.
762 repacking: Whether it's trying repack an target_files file after loading the
763 info dict (default: False). If so, it will rewrite a few loaded
764 properties (e.g. selinux_fc, root_dir) to point to the actual files in
765 target_files file. When doing repacking, `input_file` must be a dir.
766
767 Returns:
768 A dict that contains the parsed key/value pairs.
769
770 Raises:
771 AssertionError: On invalid input arguments.
772 ValueError: On malformed input values.
773 """
774 if repacking:
775 assert isinstance(input_file, str), \
776 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700777
Doug Zongkerc9253822014-02-04 12:17:58 -0800778 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000779 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800780
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700781 try:
Michael Runge6e836112014-04-15 17:40:21 -0700782 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700783 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700784 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700785
Tao Bao410ad8b2018-08-24 12:08:38 -0700786 if "recovery_api_version" not in d:
787 raise ValueError("Failed to find 'recovery_api_version'")
788 if "fstab_version" not in d:
789 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800790
Tao Bao410ad8b2018-08-24 12:08:38 -0700791 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700792 # "selinux_fc" properties should point to the file_contexts files
793 # (file_contexts.bin) under META/.
794 for key in d:
795 if key.endswith("selinux_fc"):
796 fc_basename = os.path.basename(d[key])
797 fc_config = os.path.join(input_file, "META", fc_basename)
798 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700799
Daniel Norman72c626f2019-05-13 15:58:14 -0700800 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700801
Tom Cherryd14b8952018-08-09 14:26:00 -0700802 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700803 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700804 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700805 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700806
David Anderson0ec64ac2019-12-06 12:21:18 -0800807 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700808 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000809 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800810 key_name = part_name + "_base_fs_file"
811 if key_name not in d:
812 continue
813 basename = os.path.basename(d[key_name])
814 base_fs_file = os.path.join(input_file, "META", basename)
815 if os.path.exists(base_fs_file):
816 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700817 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700818 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800819 "Failed to find %s base fs file: %s", part_name, base_fs_file)
820 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700821
Doug Zongker37974732010-09-16 17:44:38 -0700822 def makeint(key):
823 if key in d:
824 d[key] = int(d[key], 0)
825
826 makeint("recovery_api_version")
827 makeint("blocksize")
828 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700829 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700830 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700831 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700832 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800833 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700834
Steve Muckle903a1ca2020-05-07 17:32:10 -0700835 boot_images = "boot.img"
836 if "boot_images" in d:
837 boot_images = d["boot_images"]
838 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400839 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700840
Tao Bao765668f2019-10-04 22:03:00 -0700841 # Load recovery fstab if applicable.
842 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800843 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800844
Tianjie Xu861f4132018-09-12 11:49:33 -0700845 # Tries to load the build props for all partitions with care_map, including
846 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800847 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800848 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000849 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800850 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700851 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800852
Tao Bao3ed35d32019-10-07 20:48:48 -0700853 # Set up the salt (based on fingerprint) that will be used when adding AVB
854 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800855 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700856 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800857 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800858 fingerprint = build_info.GetPartitionFingerprint(partition)
859 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400860 d["avb_{}_salt".format(partition)] = sha256(
861 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700862
863 # Set the vbmeta digest if exists
864 try:
865 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
866 except KeyError:
867 pass
868
Kelvin Zhang39aea442020-08-17 11:04:25 -0400869 try:
870 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
871 except KeyError:
872 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700873 return d
874
Tao Baod1de6f32017-03-01 16:38:48 -0800875
Daniel Norman4cc9df62019-07-18 10:11:07 -0700876def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900877 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700878 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900879
Daniel Norman4cc9df62019-07-18 10:11:07 -0700880
881def LoadDictionaryFromFile(file_path):
882 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900883 return LoadDictionaryFromLines(lines)
884
885
Michael Runge6e836112014-04-15 17:40:21 -0700886def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700887 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700888 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700889 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700890 if not line or line.startswith("#"):
891 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700892 if "=" in line:
893 name, value = line.split("=", 1)
894 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700895 return d
896
Tao Baod1de6f32017-03-01 16:38:48 -0800897
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000898class PartitionBuildProps(object):
899 """The class holds the build prop of a particular partition.
900
901 This class loads the build.prop and holds the build properties for a given
902 partition. It also partially recognizes the 'import' statement in the
903 build.prop; and calculates alternative values of some specific build
904 properties during runtime.
905
906 Attributes:
907 input_file: a zipped target-file or an unzipped target-file directory.
908 partition: name of the partition.
909 props_allow_override: a list of build properties to search for the
910 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000911 build_props: a dict of build properties for the given partition.
912 prop_overrides: a set of props that are overridden by import.
913 placeholder_values: A dict of runtime variables' values to replace the
914 placeholders in the build.prop file. We expect exactly one value for
915 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800916 ramdisk_format: If name is "boot", the format of ramdisk inside the
917 boot image. Otherwise, its value is ignored.
918 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000919 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400920
Tianjie Xu9afb2212020-05-10 21:48:15 +0000921 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000922 self.input_file = input_file
923 self.partition = name
924 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000925 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000926 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000927 self.prop_overrides = set()
928 self.placeholder_values = {}
929 if placeholder_values:
930 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000931
932 @staticmethod
933 def FromDictionary(name, build_props):
934 """Constructs an instance from a build prop dictionary."""
935
936 props = PartitionBuildProps("unknown", name)
937 props.build_props = build_props.copy()
938 return props
939
940 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800941 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000942 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800943
Devin Mooreafdd7c72021-12-13 22:04:08 +0000944 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400945 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000946 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800947 else:
948 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
949
950 props = PartitionBuildProps(input_file, name, placeholder_values)
951 props._LoadBuildProp(data)
952 return props
953
954 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000955 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800956 """
957 Read build.prop for boot image from input_file.
958 Return empty string if not found.
959 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000960 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800961 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000962 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800963 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000964 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800965 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800966 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800967 if prop_file is None:
968 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500969 with open(prop_file, "r") as f:
970 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800971
972 @staticmethod
973 def _ReadPartitionPropFile(input_file, name):
974 """
975 Read build.prop for name from input_file.
976 Return empty string if not found.
977 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000978 data = ''
979 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
980 '{}/build.prop'.format(name.upper())]:
981 try:
982 data = ReadFromInputFile(input_file, prop_file)
983 break
984 except KeyError:
985 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800986 if data == '':
987 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800988 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000989
Yifan Hong125d0b62020-09-24 17:07:03 -0700990 @staticmethod
991 def FromBuildPropFile(name, build_prop_file):
992 """Constructs an instance from a build prop file."""
993
994 props = PartitionBuildProps("unknown", name)
995 with open(build_prop_file) as f:
996 props._LoadBuildProp(f.read())
997 return props
998
Tianjie Xu9afb2212020-05-10 21:48:15 +0000999 def _LoadBuildProp(self, data):
1000 for line in data.split('\n'):
1001 line = line.strip()
1002 if not line or line.startswith("#"):
1003 continue
1004 if line.startswith("import"):
1005 overrides = self._ImportParser(line)
1006 duplicates = self.prop_overrides.intersection(overrides.keys())
1007 if duplicates:
1008 raise ValueError('prop {} is overridden multiple times'.format(
1009 ','.join(duplicates)))
1010 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1011 self.build_props.update(overrides)
1012 elif "=" in line:
1013 name, value = line.split("=", 1)
1014 if name in self.prop_overrides:
1015 raise ValueError('prop {} is set again after overridden by import '
1016 'statement'.format(name))
1017 self.build_props[name] = value
1018
1019 def _ImportParser(self, line):
1020 """Parses the build prop in a given import statement."""
1021
1022 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001023 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001024 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001025
1026 if len(tokens) == 3:
1027 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1028 return {}
1029
Tianjie Xu9afb2212020-05-10 21:48:15 +00001030 import_path = tokens[1]
1031 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001032 logger.warn('Unrecognized import path {}'.format(line))
1033 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001034
1035 # We only recognize a subset of import statement that the init process
1036 # supports. And we can loose the restriction based on how the dynamic
1037 # fingerprint is used in practice. The placeholder format should be
1038 # ${placeholder}, and its value should be provided by the caller through
1039 # the placeholder_values.
1040 for prop, value in self.placeholder_values.items():
1041 prop_place_holder = '${{{}}}'.format(prop)
1042 if prop_place_holder in import_path:
1043 import_path = import_path.replace(prop_place_holder, value)
1044 if '$' in import_path:
1045 logger.info('Unresolved place holder in import path %s', import_path)
1046 return {}
1047
1048 import_path = import_path.replace('/{}'.format(self.partition),
1049 self.partition.upper())
1050 logger.info('Parsing build props override from %s', import_path)
1051
1052 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1053 d = LoadDictionaryFromLines(lines)
1054 return {key: val for key, val in d.items()
1055 if key in self.props_allow_override}
1056
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001057 def GetProp(self, prop):
1058 return self.build_props.get(prop)
1059
1060
Tianjie Xucfa86222016-03-07 16:31:19 -08001061def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1062 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001063 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001064 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001065 self.mount_point = mount_point
1066 self.fs_type = fs_type
1067 self.device = device
1068 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001069 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001070 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001071
1072 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001073 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001074 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001075 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001076 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001077
Tao Baod1de6f32017-03-01 16:38:48 -08001078 assert fstab_version == 2
1079
1080 d = {}
1081 for line in data.split("\n"):
1082 line = line.strip()
1083 if not line or line.startswith("#"):
1084 continue
1085
1086 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1087 pieces = line.split()
1088 if len(pieces) != 5:
1089 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1090
1091 # Ignore entries that are managed by vold.
1092 options = pieces[4]
1093 if "voldmanaged=" in options:
1094 continue
1095
1096 # It's a good line, parse it.
1097 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001098 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001099 options = options.split(",")
1100 for i in options:
1101 if i.startswith("length="):
1102 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001103 elif i == "slotselect":
1104 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001105 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001106 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001107 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001108
Tao Baod1de6f32017-03-01 16:38:48 -08001109 mount_flags = pieces[3]
1110 # Honor the SELinux context if present.
1111 context = None
1112 for i in mount_flags.split(","):
1113 if i.startswith("context="):
1114 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001115
Tao Baod1de6f32017-03-01 16:38:48 -08001116 mount_point = pieces[1]
Brint E. Kriebel78222aa2018-02-14 23:02:06 -08001117 if not d.get(mount_point):
1118 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
1119 device=pieces[0], length=length, context=context,
1120 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001121
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001122 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001123 # system. Other areas assume system is always at "/system" so point /system
1124 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001125 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001126 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001127 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001128 return d
1129
1130
Tao Bao765668f2019-10-04 22:03:00 -07001131def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1132 """Finds the path to recovery fstab and loads its contents."""
1133 # recovery fstab is only meaningful when installing an update via recovery
1134 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001135 if info_dict.get('ab_update') == 'true' and \
1136 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001137 return None
1138
1139 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1140 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1141 # cases, since it may load the info_dict from an old build (e.g. when
1142 # generating incremental OTAs from that build).
1143 system_root_image = info_dict.get('system_root_image') == 'true'
1144 if info_dict.get('no_recovery') != 'true':
1145 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1146 if isinstance(input_file, zipfile.ZipFile):
1147 if recovery_fstab_path not in input_file.namelist():
1148 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1149 else:
1150 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1151 if not os.path.exists(path):
1152 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1153 return LoadRecoveryFSTab(
1154 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1155 system_root_image)
1156
1157 if info_dict.get('recovery_as_boot') == 'true':
1158 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1159 if isinstance(input_file, zipfile.ZipFile):
1160 if recovery_fstab_path not in input_file.namelist():
1161 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1162 else:
1163 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1164 if not os.path.exists(path):
1165 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1166 return LoadRecoveryFSTab(
1167 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1168 system_root_image)
1169
1170 return None
1171
1172
Doug Zongker37974732010-09-16 17:44:38 -07001173def DumpInfoDict(d):
1174 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001175 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001176
Dan Albert8b72aef2015-03-23 19:13:21 -07001177
Daniel Norman55417142019-11-25 16:04:36 -08001178def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001179 """Merges dynamic partition info variables.
1180
1181 Args:
1182 framework_dict: The dictionary of dynamic partition info variables from the
1183 partial framework target files.
1184 vendor_dict: The dictionary of dynamic partition info variables from the
1185 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001186
1187 Returns:
1188 The merged dynamic partition info dictionary.
1189 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001190
1191 def uniq_concat(a, b):
1192 combined = set(a.split(" "))
1193 combined.update(set(b.split(" ")))
1194 combined = [item.strip() for item in combined if item.strip()]
1195 return " ".join(sorted(combined))
1196
1197 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf4406ca2022-05-02 12:19:45 -07001198 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001199 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1200
1201 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhangf4406ca2022-05-02 12:19:45 -07001202 # For keys-value pairs that are the same, copy to merged dict
1203 for key in vendor_dict.keys():
1204 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1205 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001206
1207 merged_dict["dynamic_partition_list"] = uniq_concat(
1208 framework_dict.get("dynamic_partition_list", ""),
1209 vendor_dict.get("dynamic_partition_list", ""))
1210
1211 # Super block devices are defined by the vendor dict.
1212 if "super_block_devices" in vendor_dict:
1213 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1214 for block_device in merged_dict["super_block_devices"].split(" "):
1215 key = "super_%s_device_size" % block_device
1216 if key not in vendor_dict:
1217 raise ValueError("Vendor dict does not contain required key %s." % key)
1218 merged_dict[key] = vendor_dict[key]
1219
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001220 # Partition groups and group sizes are defined by the vendor dict because
1221 # these values may vary for each board that uses a shared system image.
1222 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001223 for partition_group in merged_dict["super_partition_groups"].split(" "):
1224 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001225 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001226 if key not in vendor_dict:
1227 raise ValueError("Vendor dict does not contain required key %s." % key)
1228 merged_dict[key] = vendor_dict[key]
1229
1230 # Set the partition group's partition list using a concatenation of the
1231 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001232 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001233 merged_dict[key] = uniq_concat(
1234 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301235
Daniel Normanb0c75912020-09-24 14:30:21 -07001236 # Various other flags should be copied from the vendor dict, if defined.
1237 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1238 "super_metadata_device", "super_partition_error_limit",
1239 "super_partition_size"):
1240 if key in vendor_dict.keys():
1241 merged_dict[key] = vendor_dict[key]
1242
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001243 return merged_dict
1244
1245
Daniel Norman21c34f72020-11-11 17:25:50 -08001246def PartitionMapFromTargetFiles(target_files_dir):
1247 """Builds a map from partition -> path within an extracted target files directory."""
1248 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1249 possible_subdirs = {
1250 "system": ["SYSTEM"],
1251 "vendor": ["VENDOR", "SYSTEM/vendor"],
1252 "product": ["PRODUCT", "SYSTEM/product"],
1253 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1254 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1255 "vendor_dlkm": [
1256 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1257 ],
1258 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001259 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001260 }
1261 partition_map = {}
1262 for partition, subdirs in possible_subdirs.items():
1263 for subdir in subdirs:
1264 if os.path.exists(os.path.join(target_files_dir, subdir)):
1265 partition_map[partition] = subdir
1266 break
1267 return partition_map
1268
1269
Daniel Normand3351562020-10-29 12:33:11 -07001270def SharedUidPartitionViolations(uid_dict, partition_groups):
1271 """Checks for APK sharedUserIds that cross partition group boundaries.
1272
1273 This uses a single or merged build's shareduid_violation_modules.json
1274 output file, as generated by find_shareduid_violation.py or
1275 core/tasks/find-shareduid-violation.mk.
1276
1277 An error is defined as a sharedUserId that is found in a set of partitions
1278 that span more than one partition group.
1279
1280 Args:
1281 uid_dict: A dictionary created by using the standard json module to read a
1282 complete shareduid_violation_modules.json file.
1283 partition_groups: A list of groups, where each group is a list of
1284 partitions.
1285
1286 Returns:
1287 A list of error messages.
1288 """
1289 errors = []
1290 for uid, partitions in uid_dict.items():
1291 found_in_groups = [
1292 group for group in partition_groups
1293 if set(partitions.keys()) & set(group)
1294 ]
1295 if len(found_in_groups) > 1:
1296 errors.append(
1297 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1298 % (uid, ",".join(sorted(partitions.keys()))))
1299 return errors
1300
1301
Daniel Norman21c34f72020-11-11 17:25:50 -08001302def RunHostInitVerifier(product_out, partition_map):
1303 """Runs host_init_verifier on the init rc files within partitions.
1304
1305 host_init_verifier searches the etc/init path within each partition.
1306
1307 Args:
1308 product_out: PRODUCT_OUT directory, containing partition directories.
1309 partition_map: A map of partition name -> relative path within product_out.
1310 """
1311 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1312 cmd = ["host_init_verifier"]
1313 for partition, path in partition_map.items():
1314 if partition not in allowed_partitions:
1315 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1316 partition)
1317 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1318 # Add --property-contexts if the file exists on the partition.
1319 property_contexts = "%s_property_contexts" % (
1320 "plat" if partition == "system" else partition)
1321 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1322 property_contexts)
1323 if os.path.exists(property_contexts_path):
1324 cmd.append("--property-contexts=%s" % property_contexts_path)
1325 # Add the passwd file if the file exists on the partition.
1326 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1327 if os.path.exists(passwd_path):
1328 cmd.extend(["-p", passwd_path])
1329 return RunAndCheckOutput(cmd)
1330
1331
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001332def AppendAVBSigningArgs(cmd, partition):
1333 """Append signing arguments for avbtool."""
1334 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1335 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001336 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1337 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1338 if os.path.exists(new_key_path):
1339 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001340 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1341 if key_path and algorithm:
1342 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001343 avb_salt = OPTIONS.info_dict.get("avb_salt")
1344 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001345 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001346 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001347
1348
Tao Bao765668f2019-10-04 22:03:00 -07001349def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001350 """Returns the VBMeta arguments for partition.
1351
1352 It sets up the VBMeta argument by including the partition descriptor from the
1353 given 'image', or by configuring the partition as a chained partition.
1354
1355 Args:
1356 partition: The name of the partition (e.g. "system").
1357 image: The path to the partition image.
1358 info_dict: A dict returned by common.LoadInfoDict(). Will use
1359 OPTIONS.info_dict if None has been given.
1360
1361 Returns:
1362 A list of VBMeta arguments.
1363 """
1364 if info_dict is None:
1365 info_dict = OPTIONS.info_dict
1366
1367 # Check if chain partition is used.
1368 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001369 if not key_path:
1370 return ["--include_descriptors_from_image", image]
1371
1372 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1373 # into vbmeta.img. The recovery image will be configured on an independent
1374 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1375 # See details at
1376 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001377 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001378 return []
1379
1380 # Otherwise chain the partition into vbmeta.
1381 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1382 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001383
1384
Tao Bao02a08592018-07-22 12:40:45 -07001385def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1386 """Constructs and returns the arg to build or verify a chained partition.
1387
1388 Args:
1389 partition: The partition name.
1390 info_dict: The info dict to look up the key info and rollback index
1391 location.
1392 key: The key to be used for building or verifying the partition. Defaults to
1393 the key listed in info_dict.
1394
1395 Returns:
1396 A string of form "partition:rollback_index_location:key" that can be used to
1397 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001398 """
1399 if key is None:
1400 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001401 if key and not os.path.exists(key) and OPTIONS.search_path:
1402 new_key_path = os.path.join(OPTIONS.search_path, key)
1403 if os.path.exists(new_key_path):
1404 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001405 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001406 rollback_index_location = info_dict[
1407 "avb_" + partition + "_rollback_index_location"]
1408 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1409
1410
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001411def _HasGkiCertificationArgs():
1412 return ("gki_signing_key_path" in OPTIONS.info_dict and
1413 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001414
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001415
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001416def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001417 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001418 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001419
1420 if not os.path.exists(key_path) and OPTIONS.search_path:
1421 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1422 if os.path.exists(new_key_path):
1423 key_path = new_key_path
1424
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001425 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001426 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001427 raise ExternalError(
1428 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001429
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001430 output_certificate = tempfile.NamedTemporaryFile()
1431 cmd = [
1432 "generate_gki_certificate",
1433 "--name", image_name,
1434 "--algorithm", algorithm,
1435 "--key", key_path,
1436 "--output", output_certificate.name,
1437 image,
1438 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001439
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001440 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1441 signature_args = signature_args.strip()
1442 if signature_args:
1443 cmd.extend(["--additional_avb_args", signature_args])
1444
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001445 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001446 args = args.strip()
1447 if args:
1448 cmd.extend(["--additional_avb_args", args])
1449
1450 RunAndCheckOutput(cmd)
1451
1452 output_certificate.seek(os.SEEK_SET, 0)
1453 data = output_certificate.read()
1454 output_certificate.close()
1455 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001456
1457
Daniel Norman276f0622019-07-26 14:13:51 -07001458def BuildVBMeta(image_path, partitions, name, needed_partitions):
1459 """Creates a VBMeta image.
1460
1461 It generates the requested VBMeta image. The requested image could be for
1462 top-level or chained VBMeta image, which is determined based on the name.
1463
1464 Args:
1465 image_path: The output path for the new VBMeta image.
1466 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001467 values. Only valid partition names are accepted, as partitions listed
1468 in common.AVB_PARTITIONS and custom partitions listed in
1469 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001470 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1471 needed_partitions: Partitions whose descriptors should be included into the
1472 generated VBMeta image.
1473
1474 Raises:
1475 AssertionError: On invalid input args.
1476 """
1477 avbtool = OPTIONS.info_dict["avb_avbtool"]
1478 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1479 AppendAVBSigningArgs(cmd, name)
1480
Hongguang Chenf23364d2020-04-27 18:36:36 -07001481 custom_partitions = OPTIONS.info_dict.get(
1482 "avb_custom_images_partition_list", "").strip().split()
1483
Daniel Norman276f0622019-07-26 14:13:51 -07001484 for partition, path in partitions.items():
1485 if partition not in needed_partitions:
1486 continue
1487 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001488 partition in AVB_VBMETA_PARTITIONS or
1489 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001490 'Unknown partition: {}'.format(partition)
1491 assert os.path.exists(path), \
1492 'Failed to find {} for {}'.format(path, partition)
1493 cmd.extend(GetAvbPartitionArg(partition, path))
1494
1495 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1496 if args and args.strip():
1497 split_args = shlex.split(args)
1498 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001499 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001500 # as a path relative to source tree, which may not be available at the
1501 # same location when running this script (we have the input target_files
1502 # zip only). For such cases, we additionally scan other locations (e.g.
1503 # IMAGES/, RADIO/, etc) before bailing out.
1504 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001505 chained_image = split_args[index + 1]
1506 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001507 continue
1508 found = False
1509 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1510 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001511 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001512 if os.path.exists(alt_path):
1513 split_args[index + 1] = alt_path
1514 found = True
1515 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001516 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001517 cmd.extend(split_args)
1518
1519 RunAndCheckOutput(cmd)
1520
1521
jiajia tang836f76b2021-04-02 14:48:26 +08001522def _MakeRamdisk(sourcedir, fs_config_file=None,
1523 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001524 ramdisk_img = tempfile.NamedTemporaryFile()
1525
1526 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1527 cmd = ["mkbootfs", "-f", fs_config_file,
1528 os.path.join(sourcedir, "RAMDISK")]
1529 else:
1530 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1531 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001532 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001533 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001534 stdout=ramdisk_img.file.fileno())
Luca Stefani7f2913c2020-06-11 13:03:18 +02001535 elif ramdisk_format == RamdiskFormat.XZ:
1536 p2 = Run(["xz", "-f", "-c", "--check=crc32", "--lzma2=dict=32MiB"], stdin=p1.stdout,
1537 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001538 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001539 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001540 else:
Luca Stefani7f2913c2020-06-11 13:03:18 +02001541 raise ValueError("Only support lz4, xz, or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001542
1543 p2.wait()
1544 p1.wait()
1545 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001546 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001547
1548 return ramdisk_img
1549
1550
Steve Muckle9793cf62020-04-08 18:27:00 -07001551def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001552 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001553 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001554
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001555 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001556 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1557 we are building a two-step special image (i.e. building a recovery image to
1558 be loaded into /boot in two-step OTAs).
1559
1560 Return the image data, or None if sourcedir does not appear to contains files
1561 for building the requested image.
1562 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001563
Yifan Hong63c5ca12020-10-08 11:54:02 -07001564 if info_dict is None:
1565 info_dict = OPTIONS.info_dict
1566
Steve Muckle9793cf62020-04-08 18:27:00 -07001567 # "boot" or "recovery", without extension.
1568 partition_name = os.path.basename(sourcedir).lower()
1569
Yifan Hong63c5ca12020-10-08 11:54:02 -07001570 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001571 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001572 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1573 logger.info("Excluded kernel binary from recovery image.")
1574 else:
1575 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001576 elif partition_name == "init_boot":
1577 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001578 else:
1579 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001580 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001581 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001582 return None
1583
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001584 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1585
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001586 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001587 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001588
Doug Zongkereef39442009-04-02 12:14:19 -07001589 img = tempfile.NamedTemporaryFile()
1590
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001591 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001592 ramdisk_format = _GetRamdiskFormat(info_dict)
1593 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1594 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001595
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001596 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1597 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1598
Yifan Hong63c5ca12020-10-08 11:54:02 -07001599 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001600 if kernel_path is not None:
1601 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001602
Benoit Fradina45a8682014-07-14 21:00:43 +02001603 fn = os.path.join(sourcedir, "second")
1604 if os.access(fn, os.F_OK):
1605 cmd.append("--second")
1606 cmd.append(fn)
1607
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001608 fn = os.path.join(sourcedir, "dtb")
1609 if os.access(fn, os.F_OK):
1610 cmd.append("--dtb")
1611 cmd.append(fn)
1612
Doug Zongker171f1cd2009-06-15 22:36:37 -07001613 fn = os.path.join(sourcedir, "cmdline")
1614 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001615 cmd.append("--cmdline")
1616 cmd.append(open(fn).read().rstrip("\n"))
1617
1618 fn = os.path.join(sourcedir, "base")
1619 if os.access(fn, os.F_OK):
1620 cmd.append("--base")
1621 cmd.append(open(fn).read().rstrip("\n"))
1622
Ying Wang4de6b5b2010-08-25 14:29:34 -07001623 fn = os.path.join(sourcedir, "pagesize")
1624 if os.access(fn, os.F_OK):
1625 cmd.append("--pagesize")
1626 cmd.append(open(fn).read().rstrip("\n"))
1627
David Ng296bfaa2012-07-27 18:39:48 -07001628 fn = os.path.join(sourcedir, "dt")
1629 if os.access(fn, os.F_OK):
1630 cmd.append("--dt")
1631 cmd.append(fn)
1632
Steve Mucklef84668e2020-03-16 19:13:46 -07001633 if partition_name == "recovery":
1634 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301635 if not args:
1636 # Fall back to "mkbootimg_args" for recovery image
1637 # in case "recovery_mkbootimg_args" is not set.
1638 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001639 elif partition_name == "init_boot":
1640 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001641 else:
1642 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001643 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001644 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001645
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001646 args = info_dict.get("mkbootimg_version_args")
1647 if args and args.strip():
1648 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001649
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001650 if has_ramdisk:
1651 cmd.extend(["--ramdisk", ramdisk_img.name])
1652
Tao Baod95e9fd2015-03-29 23:07:41 -07001653 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001654 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001655 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001656 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001657 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001658 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001659
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001660 if partition_name == "recovery":
1661 if info_dict.get("include_recovery_dtbo") == "true":
1662 fn = os.path.join(sourcedir, "recovery_dtbo")
1663 cmd.extend(["--recovery_dtbo", fn])
1664 if info_dict.get("include_recovery_acpio") == "true":
1665 fn = os.path.join(sourcedir, "recovery_acpio")
1666 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001667
Tao Bao986ee862018-10-04 15:46:16 -07001668 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001669
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001670 if _HasGkiCertificationArgs():
1671 if not os.path.exists(img.name):
1672 raise ValueError("Cannot find GKI boot.img")
1673 if kernel_path is None or not os.path.exists(kernel_path):
1674 raise ValueError("Cannot find GKI kernel.img")
1675
1676 # Certify GKI images.
1677 boot_signature_bytes = b''
1678 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1679 boot_signature_bytes += _GenerateGkiCertificate(
1680 kernel_path, "generic_kernel")
1681
1682 BOOT_SIGNATURE_SIZE = 16 * 1024
1683 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1684 raise ValueError(
1685 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1686 boot_signature_bytes += (
1687 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1688 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1689
1690 with open(img.name, 'ab') as f:
1691 f.write(boot_signature_bytes)
1692
Tao Bao76def242017-11-21 09:25:31 -08001693 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001694 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001695 # Hard-code the path as "/boot" for two-step special recovery image (which
1696 # will be loaded into /boot during the two-step OTA).
1697 if two_step_image:
1698 path = "/boot"
1699 else:
Tao Baobf70c312017-07-11 17:27:55 -07001700 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001701 cmd = [OPTIONS.boot_signer_path]
1702 cmd.extend(OPTIONS.boot_signer_args)
1703 cmd.extend([path, img.name,
1704 info_dict["verity_key"] + ".pk8",
1705 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001706 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001707
Tao Baod95e9fd2015-03-29 23:07:41 -07001708 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001709 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001710 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001711 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001712 # We have switched from the prebuilt futility binary to using the tool
1713 # (futility-host) built from the source. Override the setting in the old
1714 # TF.zip.
1715 futility = info_dict["futility"]
1716 if futility.startswith("prebuilts/"):
1717 futility = "futility-host"
1718 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001719 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001720 info_dict["vboot_key"] + ".vbprivk",
1721 info_dict["vboot_subkey"] + ".vbprivk",
1722 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001723 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001724 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001725
Tao Baof3282b42015-04-01 11:21:55 -07001726 # Clean up the temp files.
1727 img_unsigned.close()
1728 img_keyblock.close()
1729
David Zeuthen8fecb282017-12-01 16:24:01 -05001730 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001731 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001732 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001733 if partition_name == "recovery":
1734 part_size = info_dict["recovery_size"]
1735 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001736 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001737 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001738 "--partition_size", str(part_size), "--partition_name",
1739 partition_name]
1740 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001741 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001742 if args and args.strip():
1743 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001744 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001745
1746 img.seek(os.SEEK_SET, 0)
1747 data = img.read()
1748
1749 if has_ramdisk:
1750 ramdisk_img.close()
1751 img.close()
1752
1753 return data
1754
1755
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001756def _SignBootableImage(image_path, prebuilt_name, partition_name,
1757 info_dict=None):
1758 """Performs AVB signing for a prebuilt boot.img.
1759
1760 Args:
1761 image_path: The full path of the image, e.g., /path/to/boot.img.
1762 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001763 boot-5.10.img, recovery.img or init_boot.img.
1764 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001765 info_dict: The information dict read from misc_info.txt.
1766 """
1767 if info_dict is None:
1768 info_dict = OPTIONS.info_dict
1769
1770 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1771 if info_dict.get("avb_enable") == "true":
1772 avbtool = info_dict["avb_avbtool"]
1773 if partition_name == "recovery":
1774 part_size = info_dict["recovery_size"]
1775 else:
1776 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1777
1778 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1779 "--partition_size", str(part_size), "--partition_name",
1780 partition_name]
1781 AppendAVBSigningArgs(cmd, partition_name)
1782 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1783 if args and args.strip():
1784 cmd.extend(shlex.split(args))
1785 RunAndCheckOutput(cmd)
1786
1787
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001788def HasRamdisk(partition_name, info_dict=None):
1789 """Returns true/false to see if a bootable image should have a ramdisk.
1790
1791 Args:
1792 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1793 info_dict: The information dict read from misc_info.txt.
1794 """
1795 if info_dict is None:
1796 info_dict = OPTIONS.info_dict
1797
1798 if partition_name != "boot":
1799 return True # init_boot.img or recovery.img has a ramdisk.
1800
1801 if info_dict.get("recovery_as_boot") == "true":
1802 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1803
Bowgo Tsai85578e02022-04-19 10:50:59 +08001804 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1805 return False # A GKI boot.img has no ramdisk since Android-13.
1806
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001807 if info_dict.get("system_root_image") == "true":
1808 # The ramdisk content is merged into the system.img, so there is NO
1809 # ramdisk in the boot.img or boot-<kernel version>.img.
1810 return False
1811
1812 if info_dict.get("init_boot") == "true":
1813 # The ramdisk is moved to the init_boot.img, so there is NO
1814 # ramdisk in the boot.img or boot-<kernel version>.img.
1815 return False
1816
1817 return True
1818
1819
Doug Zongkerd5131602012-08-02 14:46:42 -07001820def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001821 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001822 """Return a File object with the desired bootable image.
1823
1824 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1825 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1826 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001827
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001828 if info_dict is None:
1829 info_dict = OPTIONS.info_dict
1830
Doug Zongker55d93282011-01-25 17:03:34 -08001831 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1832 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001833 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001834 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001835
1836 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1837 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001838 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001839 return File.FromLocalFile(name, prebuilt_path)
1840
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001841 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001842 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1843 if os.path.exists(prebuilt_path):
1844 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1845 signed_img = MakeTempFile()
1846 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001847 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1848 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001849
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001850 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001851
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001852 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001853
Doug Zongker6f1d0312014-08-22 08:07:12 -07001854 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001855 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001856 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001857 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001858 if data:
1859 return File(name, data)
1860 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001861
Doug Zongkereef39442009-04-02 12:14:19 -07001862
Lucas Wei03230252022-04-18 16:00:40 +08001863def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001864 """Build a vendor boot image from the specified sourcedir.
1865
1866 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1867 turn them into a vendor boot image.
1868
1869 Return the image data, or None if sourcedir does not appear to contains files
1870 for building the requested image.
1871 """
1872
1873 if info_dict is None:
1874 info_dict = OPTIONS.info_dict
1875
1876 img = tempfile.NamedTemporaryFile()
1877
jiajia tang836f76b2021-04-02 14:48:26 +08001878 ramdisk_format = _GetRamdiskFormat(info_dict)
1879 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001880
1881 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1882 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1883
1884 cmd = [mkbootimg]
1885
1886 fn = os.path.join(sourcedir, "dtb")
1887 if os.access(fn, os.F_OK):
Lucas Wei03230252022-04-18 16:00:40 +08001888 has_vendor_kernel_boot = (info_dict.get("vendor_kernel_boot", "").lower() == "true")
1889
1890 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1891 # Otherwise pack dtb into vendor_boot.
1892 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1893 cmd.append("--dtb")
1894 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001895
1896 fn = os.path.join(sourcedir, "vendor_cmdline")
1897 if os.access(fn, os.F_OK):
1898 cmd.append("--vendor_cmdline")
1899 cmd.append(open(fn).read().rstrip("\n"))
1900
1901 fn = os.path.join(sourcedir, "base")
1902 if os.access(fn, os.F_OK):
1903 cmd.append("--base")
1904 cmd.append(open(fn).read().rstrip("\n"))
1905
1906 fn = os.path.join(sourcedir, "pagesize")
1907 if os.access(fn, os.F_OK):
1908 cmd.append("--pagesize")
1909 cmd.append(open(fn).read().rstrip("\n"))
1910
1911 args = info_dict.get("mkbootimg_args")
1912 if args and args.strip():
1913 cmd.extend(shlex.split(args))
1914
1915 args = info_dict.get("mkbootimg_version_args")
1916 if args and args.strip():
1917 cmd.extend(shlex.split(args))
1918
1919 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1920 cmd.extend(["--vendor_boot", img.name])
1921
Devin Moore50509012021-01-13 10:45:04 -08001922 fn = os.path.join(sourcedir, "vendor_bootconfig")
1923 if os.access(fn, os.F_OK):
1924 cmd.append("--vendor_bootconfig")
1925 cmd.append(fn)
1926
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001927 ramdisk_fragment_imgs = []
1928 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1929 if os.access(fn, os.F_OK):
1930 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1931 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001932 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1933 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001934 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001935 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1936 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001937 # Use prebuilt image if found, else create ramdisk from supplied files.
1938 if os.access(fn, os.F_OK):
1939 ramdisk_fragment_pathname = fn
1940 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001941 ramdisk_fragment_root = os.path.join(
1942 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001943 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1944 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001945 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1946 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1947 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1948
Steve Mucklee1b10862019-07-10 10:49:37 -07001949 RunAndCheckOutput(cmd)
1950
1951 # AVB: if enabled, calculate and add hash.
1952 if info_dict.get("avb_enable") == "true":
1953 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001954 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001955 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001956 "--partition_size", str(part_size), "--partition_name", partition_name]
1957 AppendAVBSigningArgs(cmd, partition_name)
1958 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001959 if args and args.strip():
1960 cmd.extend(shlex.split(args))
1961 RunAndCheckOutput(cmd)
1962
1963 img.seek(os.SEEK_SET, 0)
1964 data = img.read()
1965
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001966 for f in ramdisk_fragment_imgs:
1967 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001968 ramdisk_img.close()
1969 img.close()
1970
1971 return data
1972
1973
1974def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1975 info_dict=None):
1976 """Return a File object with the desired vendor boot image.
1977
1978 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1979 the source files in 'unpack_dir'/'tree_subdir'."""
1980
1981 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1982 if os.path.exists(prebuilt_path):
1983 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1984 return File.FromLocalFile(name, prebuilt_path)
1985
1986 logger.info("building image from target_files %s...", tree_subdir)
1987
1988 if info_dict is None:
1989 info_dict = OPTIONS.info_dict
1990
Kelvin Zhang0876c412020-06-23 15:06:58 -04001991 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001992 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1993 if data:
1994 return File(name, data)
1995 return None
1996
1997
1998def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1999 info_dict=None):
2000 """Return a File object with the desired vendor kernel boot image.
2001
2002 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2003 the source files in 'unpack_dir'/'tree_subdir'."""
2004
2005 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2006 if os.path.exists(prebuilt_path):
2007 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2008 return File.FromLocalFile(name, prebuilt_path)
2009
2010 logger.info("building image from target_files %s...", tree_subdir)
2011
2012 if info_dict is None:
2013 info_dict = OPTIONS.info_dict
2014
2015 data = _BuildVendorBootImage(
2016 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002017 if data:
2018 return File(name, data)
2019 return None
2020
2021
Narayan Kamatha07bf042017-08-14 14:49:21 +01002022def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002023 """Gunzips the given gzip compressed file to a given output file."""
2024 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002025 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002026 shutil.copyfileobj(in_file, out_file)
2027
2028
Kelvin Zhang5f148302023-06-21 13:06:59 -07002029def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
2030 # According to https://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip/6297838#6297838
2031 # higher bits of |external_attr| are unix file permission and types
2032 unix_filetype = info.external_attr >> 16
2033
2034 def CheckMask(a, mask):
2035 return (a & mask) == mask
2036
2037 def IsSymlink(a):
2038 return CheckMask(a, stat.S_IFLNK)
2039 # python3.11 zipfile implementation doesn't handle symlink correctly
2040 if not IsSymlink(unix_filetype):
2041 return input_zip.extract(info, dirname)
2042 if dirname is None:
2043 dirname = os.getcwd()
2044 target = os.path.join(dirname, info.filename)
2045 os.makedirs(os.path.dirname(target), exist_ok=True)
2046 os.symlink(input_zip.read(info).decode(), target)
2047
2048
Tao Bao0ff15de2019-03-20 11:26:06 -07002049def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002050 """Unzips the archive to the given directory.
2051
2052 Args:
2053 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002054 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002055 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2056 archvie. Non-matching patterns will be filtered out. If there's no match
2057 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002058 """
Kelvin Zhang9beb6a52023-06-05 09:58:16 -07002059 with zipfile.ZipFile(filename, allowZip64=True, mode="r") as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002060 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang90b66752023-06-14 12:53:29 -07002061 entries = input_zip.infolist()
2062 # b/283033491
2063 # Per https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
2064 # In zip64 mode, central directory record's header_offset field might be
2065 # set to 0xFFFFFFFF if header offset is > 2^32. In this case, the extra
2066 # fields will contain an 8 byte little endian integer at offset 20
2067 # to indicate the actual local header offset.
2068 # As of python3.11, python does not handle zip64 central directories
2069 # correctly, so we will manually do the parsing here.
Kelvin Zhang98edbd72023-06-17 09:18:15 -07002070
2071 # ZIP64 central directory extra field has two required fields:
2072 # 2 bytes header ID and 2 bytes size field. Thes two require fields have
2073 # a total size of 4 bytes. Then it has three other 8 bytes field, followed
2074 # by a 4 byte disk number field. The last disk number field is not required
2075 # to be present, but if it is present, the total size of extra field will be
2076 # divisible by 8(because 2+2+4+8*n is always going to be multiple of 8)
2077 # Most extra fields are optional, but when they appear, their must appear
2078 # in the order defined by zip64 spec. Since file header offset is the 2nd
2079 # to last field in zip64 spec, it will only be at last 8 bytes or last 12-4
2080 # bytes, depending on whether disk number is present.
Kelvin Zhang90b66752023-06-14 12:53:29 -07002081 for entry in entries:
Kelvin Zhang98edbd72023-06-17 09:18:15 -07002082 if entry.header_offset == 0xFFFFFFFF:
2083 if len(entry.extra) % 8 == 0:
2084 entry.header_offset = int.from_bytes(entry.extra[-12:-4], "little")
2085 else:
2086 entry.header_offset = int.from_bytes(entry.extra[-8:], "little")
Kelvin Zhang9beb6a52023-06-05 09:58:16 -07002087 if patterns is not None:
Kelvin Zhang90b66752023-06-14 12:53:29 -07002088 filtered = [info for info in entries if any(
2089 [fnmatch.fnmatch(info.filename, p) for p in patterns])]
Tao Bao0ff15de2019-03-20 11:26:06 -07002090
Kelvin Zhang9beb6a52023-06-05 09:58:16 -07002091 # There isn't any matching files. Don't unzip anything.
2092 if not filtered:
2093 return
Kelvin Zhang5f148302023-06-21 13:06:59 -07002094 for info in filtered:
2095 UnzipSingleFile(input_zip, info, dirname)
Kelvin Zhang9beb6a52023-06-05 09:58:16 -07002096 else:
Kelvin Zhang5f148302023-06-21 13:06:59 -07002097 for info in entries:
2098 UnzipSingleFile(input_zip, info, dirname)
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002099
2100
Daniel Norman78554ea2021-09-14 10:29:38 -07002101def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002102 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002103
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002104 Args:
2105 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2106 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2107
Daniel Norman78554ea2021-09-14 10:29:38 -07002108 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002109 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002110
Tao Bao1c830bf2017-12-25 10:43:47 -08002111 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002112 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002113 """
Doug Zongkereef39442009-04-02 12:14:19 -07002114
Tao Bao1c830bf2017-12-25 10:43:47 -08002115 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002116 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2117 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002118 UnzipToDir(m.group(1), tmp, patterns)
2119 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002120 filename = m.group(1)
2121 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002122 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002123
Tao Baodba59ee2018-01-09 13:21:02 -08002124 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002125
2126
Yifan Hong8a66a712019-04-04 15:37:57 -07002127def GetUserImage(which, tmpdir, input_zip,
2128 info_dict=None,
2129 allow_shared_blocks=None,
2130 hashtree_info_generator=None,
2131 reset_file_map=False):
2132 """Returns an Image object suitable for passing to BlockImageDiff.
2133
2134 This function loads the specified image from the given path. If the specified
2135 image is sparse, it also performs additional processing for OTA purpose. For
2136 example, it always adds block 0 to clobbered blocks list. It also detects
2137 files that cannot be reconstructed from the block list, for whom we should
2138 avoid applying imgdiff.
2139
2140 Args:
2141 which: The partition name.
2142 tmpdir: The directory that contains the prebuilt image and block map file.
2143 input_zip: The target-files ZIP archive.
2144 info_dict: The dict to be looked up for relevant info.
2145 allow_shared_blocks: If image is sparse, whether having shared blocks is
2146 allowed. If none, it is looked up from info_dict.
2147 hashtree_info_generator: If present and image is sparse, generates the
2148 hashtree_info for this sparse image.
2149 reset_file_map: If true and image is sparse, reset file map before returning
2150 the image.
2151 Returns:
2152 A Image object. If it is a sparse image and reset_file_map is False, the
2153 image will have file_map info loaded.
2154 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002155 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002156 info_dict = LoadInfoDict(input_zip)
2157
2158 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002159 if info_dict.get(which + "_disable_sparse"):
2160 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002161
2162 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2163 # shared blocks (i.e. some blocks will show up in multiple files' block
2164 # list). We can only allocate such shared blocks to the first "owner", and
2165 # disable imgdiff for all later occurrences.
2166 if allow_shared_blocks is None:
2167 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2168
2169 if is_sparse:
2170 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2171 hashtree_info_generator)
2172 if reset_file_map:
2173 img.ResetFileMap()
2174 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002175 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002176
2177
2178def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2179 """Returns a Image object suitable for passing to BlockImageDiff.
2180
2181 This function loads the specified non-sparse image from the given path.
2182
2183 Args:
2184 which: The partition name.
2185 tmpdir: The directory that contains the prebuilt image and block map file.
2186 Returns:
2187 A Image object.
2188 """
2189 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2190 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2191
2192 # The image and map files must have been created prior to calling
2193 # ota_from_target_files.py (since LMP).
2194 assert os.path.exists(path) and os.path.exists(mappath)
2195
Tianjie Xu41976c72019-07-03 13:57:01 -07002196 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2197
Yifan Hong8a66a712019-04-04 15:37:57 -07002198
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002199def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2200 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002201 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2202
2203 This function loads the specified sparse image from the given path, and
2204 performs additional processing for OTA purpose. For example, it always adds
2205 block 0 to clobbered blocks list. It also detects files that cannot be
2206 reconstructed from the block list, for whom we should avoid applying imgdiff.
2207
2208 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002209 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002210 tmpdir: The directory that contains the prebuilt image and block map file.
2211 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002212 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002213 hashtree_info_generator: If present, generates the hashtree_info for this
2214 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002215 Returns:
2216 A SparseImage object, with file_map info loaded.
2217 """
Tao Baoc765cca2018-01-31 17:32:40 -08002218 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2219 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2220
2221 # The image and map files must have been created prior to calling
2222 # ota_from_target_files.py (since LMP).
2223 assert os.path.exists(path) and os.path.exists(mappath)
2224
2225 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2226 # it to clobbered_blocks so that it will be written to the target
2227 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2228 clobbered_blocks = "0"
2229
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002230 image = sparse_img.SparseImage(
2231 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2232 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002233
2234 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2235 # if they contain all zeros. We can't reconstruct such a file from its block
2236 # list. Tag such entries accordingly. (Bug: 65213616)
2237 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002238 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002239 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002240 continue
2241
Tom Cherryd14b8952018-08-09 14:26:00 -07002242 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2243 # filename listed in system.map may contain an additional leading slash
2244 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2245 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002246 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002247 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002248 arcname = entry.lstrip('/')
2249 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002250 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002251 else:
2252 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002253
2254 assert arcname in input_zip.namelist(), \
2255 "Failed to find the ZIP entry for {}".format(entry)
2256
Tao Baoc765cca2018-01-31 17:32:40 -08002257 info = input_zip.getinfo(arcname)
2258 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002259
2260 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002261 # image, check the original block list to determine its completeness. Note
2262 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002263 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002264 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002265
Tao Baoc765cca2018-01-31 17:32:40 -08002266 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2267 ranges.extra['incomplete'] = True
2268
2269 return image
2270
2271
Doug Zongkereef39442009-04-02 12:14:19 -07002272def GetKeyPasswords(keylist):
2273 """Given a list of keys, prompt the user to enter passwords for
2274 those which require them. Return a {key: password} dict. password
2275 will be None if the key has no password."""
2276
Doug Zongker8ce7c252009-05-22 13:34:54 -07002277 no_passwords = []
2278 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002279 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002280 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002281
2282 # sorted() can't compare strings to None, so convert Nones to strings
2283 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002284 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002285 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002286 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002287 continue
2288
T.R. Fullhart37e10522013-03-18 10:31:26 -07002289 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002290 "-inform", "DER", "-nocrypt"],
2291 stdin=devnull.fileno(),
2292 stdout=devnull.fileno(),
2293 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002294 p.communicate()
2295 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002296 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002297 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002298 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002299 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2300 "-inform", "DER", "-passin", "pass:"],
2301 stdin=devnull.fileno(),
2302 stdout=devnull.fileno(),
2303 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002304 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002305 if p.returncode == 0:
2306 # Encrypted key with empty string as password.
2307 key_passwords[k] = ''
2308 elif stderr.startswith('Error decrypting key'):
2309 # Definitely encrypted key.
2310 # It would have said "Error reading key" if it didn't parse correctly.
2311 need_passwords.append(k)
2312 else:
2313 # Potentially, a type of key that openssl doesn't understand.
2314 # We'll let the routines in signapk.jar handle it.
2315 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002316 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002317
T.R. Fullhart37e10522013-03-18 10:31:26 -07002318 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002319 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002320 return key_passwords
2321
2322
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002323def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002324 """Gets the minSdkVersion declared in the APK.
2325
Martin Stjernholm58472e82022-01-07 22:08:47 +00002326 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2327 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002328
2329 Args:
2330 apk_name: The APK filename.
2331
2332 Returns:
2333 The parsed SDK version string.
2334
2335 Raises:
2336 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002337 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002338 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002339 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002340 stderr=subprocess.PIPE)
2341 stdoutdata, stderrdata = proc.communicate()
2342 if proc.returncode != 0:
2343 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002344 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2345 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002346
Tao Baof47bf0f2018-03-21 23:28:51 -07002347 for line in stdoutdata.split("\n"):
2348 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002349 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2350 if m:
2351 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002352 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002353
2354
2355def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002356 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002357
Tao Baof47bf0f2018-03-21 23:28:51 -07002358 If minSdkVersion is set to a codename, it is translated to a number using the
2359 provided map.
2360
2361 Args:
2362 apk_name: The APK filename.
2363
2364 Returns:
2365 The parsed SDK version number.
2366
2367 Raises:
2368 ExternalError: On failing to get the min SDK version number.
2369 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002370 version = GetMinSdkVersion(apk_name)
2371 try:
2372 return int(version)
2373 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002374 # Not a decimal number.
2375 #
2376 # It could be either a straight codename, e.g.
2377 # UpsideDownCake
2378 #
2379 # Or a codename with API fingerprint SHA, e.g.
2380 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2381 #
2382 # Extract the codename and try and map it to a version number.
2383 split = version.split(".")
2384 codename = split[0]
2385 if codename in codename_to_api_level_map:
2386 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002387 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002388 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2389 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002390
2391
2392def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002393 codename_to_api_level_map=None, whole_file=False,
2394 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002395 """Sign the input_name zip/jar/apk, producing output_name. Use the
2396 given key and password (the latter may be None if the key does not
2397 have a password.
2398
Doug Zongker951495f2009-08-14 12:44:19 -07002399 If whole_file is true, use the "-w" option to SignApk to embed a
2400 signature that covers the whole file in the archive comment of the
2401 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002402
2403 min_api_level is the API Level (int) of the oldest platform this file may end
2404 up on. If not specified for an APK, the API Level is obtained by interpreting
2405 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2406
2407 codename_to_api_level_map is needed to translate the codename which may be
2408 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002409
2410 Caller may optionally specify extra args to be passed to SignApk, which
2411 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002412 """
Tao Bao76def242017-11-21 09:25:31 -08002413 if codename_to_api_level_map is None:
2414 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002415 if extra_signapk_args is None:
2416 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002417
Alex Klyubin9667b182015-12-10 13:38:50 -08002418 java_library_path = os.path.join(
2419 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2420
Tao Baoe95540e2016-11-08 12:08:53 -08002421 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2422 ["-Djava.library.path=" + java_library_path,
2423 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002424 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002425 if whole_file:
2426 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002427
2428 min_sdk_version = min_api_level
2429 if min_sdk_version is None:
2430 if not whole_file:
2431 min_sdk_version = GetMinSdkVersionInt(
2432 input_name, codename_to_api_level_map)
2433 if min_sdk_version is not None:
2434 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2435
T.R. Fullhart37e10522013-03-18 10:31:26 -07002436 cmd.extend([key + OPTIONS.public_key_suffix,
2437 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002438 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002439
Tao Bao73dd4f42018-10-04 16:25:33 -07002440 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002441 if password is not None:
2442 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002443 stdoutdata, _ = proc.communicate(password)
2444 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002445 raise ExternalError(
2446 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002447 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002448
Doug Zongkereef39442009-04-02 12:14:19 -07002449
Doug Zongker37974732010-09-16 17:44:38 -07002450def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002451 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002452
Tao Bao9dd909e2017-11-14 11:27:32 -08002453 For non-AVB images, raise exception if the data is too big. Print a warning
2454 if the data is nearing the maximum size.
2455
2456 For AVB images, the actual image size should be identical to the limit.
2457
2458 Args:
2459 data: A string that contains all the data for the partition.
2460 target: The partition name. The ".img" suffix is optional.
2461 info_dict: The dict to be looked up for relevant info.
2462 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002463 if target.endswith(".img"):
2464 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002465 mount_point = "/" + target
2466
Ying Wangf8824af2014-06-03 14:07:27 -07002467 fs_type = None
2468 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002469 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002470 if mount_point == "/userdata":
2471 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002472 p = info_dict["fstab"][mount_point]
2473 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002474 device = p.device
2475 if "/" in device:
2476 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002477 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002478 if not fs_type or not limit:
2479 return
Doug Zongkereef39442009-04-02 12:14:19 -07002480
Andrew Boie0f9aec82012-02-14 09:32:52 -08002481 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002482 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2483 # path.
2484 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2485 if size != limit:
2486 raise ExternalError(
2487 "Mismatching image size for %s: expected %d actual %d" % (
2488 target, limit, size))
2489 else:
2490 pct = float(size) * 100.0 / limit
2491 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2492 if pct >= 99.0:
2493 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002494
2495 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002496 logger.warning("\n WARNING: %s\n", msg)
2497 else:
2498 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002499
2500
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002501def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002502 """Parses the APK certs info from a given target-files zip.
2503
2504 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2505 tuple with the following elements: (1) a dictionary that maps packages to
2506 certs (based on the "certificate" and "private_key" attributes in the file;
2507 (2) a string representing the extension of compressed APKs in the target files
2508 (e.g ".gz", ".bro").
2509
2510 Args:
2511 tf_zip: The input target_files ZipFile (already open).
2512
2513 Returns:
2514 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2515 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2516 no compressed APKs.
2517 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002518 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002519 compressed_extension = None
2520
Tao Bao0f990332017-09-08 19:02:54 -07002521 # META/apkcerts.txt contains the info for _all_ the packages known at build
2522 # time. Filter out the ones that are not installed.
2523 installed_files = set()
2524 for name in tf_zip.namelist():
2525 basename = os.path.basename(name)
2526 if basename:
2527 installed_files.add(basename)
2528
Tao Baoda30cfa2017-12-01 16:19:46 -08002529 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002530 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002531 if not line:
2532 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002533 m = re.match(
2534 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002535 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2536 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002537 line)
2538 if not m:
2539 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002540
Tao Bao818ddf52018-01-05 11:17:34 -08002541 matches = m.groupdict()
2542 cert = matches["CERT"]
2543 privkey = matches["PRIVKEY"]
2544 name = matches["NAME"]
2545 this_compressed_extension = matches["COMPRESSED"]
2546
2547 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2548 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2549 if cert in SPECIAL_CERT_STRINGS and not privkey:
2550 certmap[name] = cert
2551 elif (cert.endswith(OPTIONS.public_key_suffix) and
2552 privkey.endswith(OPTIONS.private_key_suffix) and
2553 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2554 certmap[name] = cert[:-public_key_suffix_len]
2555 else:
2556 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2557
2558 if not this_compressed_extension:
2559 continue
2560
2561 # Only count the installed files.
2562 filename = name + '.' + this_compressed_extension
2563 if filename not in installed_files:
2564 continue
2565
2566 # Make sure that all the values in the compression map have the same
2567 # extension. We don't support multiple compression methods in the same
2568 # system image.
2569 if compressed_extension:
2570 if this_compressed_extension != compressed_extension:
2571 raise ValueError(
2572 "Multiple compressed extensions: {} vs {}".format(
2573 compressed_extension, this_compressed_extension))
2574 else:
2575 compressed_extension = this_compressed_extension
2576
2577 return (certmap,
2578 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002579
2580
Doug Zongkereef39442009-04-02 12:14:19 -07002581COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002582Global options
2583
2584 -p (--path) <dir>
2585 Prepend <dir>/bin to the list of places to search for binaries run by this
2586 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002587
Doug Zongker05d3dea2009-06-22 11:32:31 -07002588 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002589 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002590
Tao Bao30df8b42018-04-23 15:32:53 -07002591 -x (--extra) <key=value>
2592 Add a key/value pair to the 'extras' dict, which device-specific extension
2593 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002594
Doug Zongkereef39442009-04-02 12:14:19 -07002595 -v (--verbose)
2596 Show command lines being executed.
2597
2598 -h (--help)
2599 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002600
2601 --logfile <file>
2602 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002603"""
2604
Kelvin Zhang0876c412020-06-23 15:06:58 -04002605
Doug Zongkereef39442009-04-02 12:14:19 -07002606def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002607 print(docstring.rstrip("\n"))
2608 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002609
2610
2611def ParseOptions(argv,
2612 docstring,
2613 extra_opts="", extra_long_opts=(),
2614 extra_option_handler=None):
2615 """Parse the options in argv and return any arguments that aren't
2616 flags. docstring is the calling module's docstring, to be displayed
2617 for errors and -h. extra_opts and extra_long_opts are for flags
2618 defined by the caller, which are processed by passing them to
2619 extra_option_handler."""
2620
2621 try:
2622 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002623 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002624 ["help", "verbose", "path=", "signapk_path=",
Martin Stjernholm58472e82022-01-07 22:08:47 +00002625 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002626 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002627 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2628 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002629 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002630 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002631 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002632 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002633 sys.exit(2)
2634
Doug Zongkereef39442009-04-02 12:14:19 -07002635 for o, a in opts:
2636 if o in ("-h", "--help"):
2637 Usage(docstring)
2638 sys.exit()
2639 elif o in ("-v", "--verbose"):
2640 OPTIONS.verbose = True
2641 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002642 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002643 elif o in ("--signapk_path",):
2644 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002645 elif o in ("--signapk_shared_library_path",):
2646 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002647 elif o in ("--extra_signapk_args",):
2648 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002649 elif o in ("--aapt2_path",):
2650 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002651 elif o in ("--java_path",):
2652 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002653 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002654 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002655 elif o in ("--android_jar_path",):
2656 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002657 elif o in ("--public_key_suffix",):
2658 OPTIONS.public_key_suffix = a
2659 elif o in ("--private_key_suffix",):
2660 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002661 elif o in ("--boot_signer_path",):
2662 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002663 elif o in ("--boot_signer_args",):
2664 OPTIONS.boot_signer_args = shlex.split(a)
2665 elif o in ("--verity_signer_path",):
2666 OPTIONS.verity_signer_path = a
2667 elif o in ("--verity_signer_args",):
2668 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002669 elif o in ("-s", "--device_specific"):
2670 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002671 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002672 key, value = a.split("=", 1)
2673 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002674 elif o in ("--logfile",):
2675 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002676 else:
2677 if extra_option_handler is None or not extra_option_handler(o, a):
2678 assert False, "unknown option \"%s\"" % (o,)
2679
Doug Zongker85448772014-09-09 14:59:20 -07002680 if OPTIONS.search_path:
2681 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2682 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002683
2684 return args
2685
2686
Tao Bao4c851b12016-09-19 13:54:38 -07002687def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002688 """Make a temp file and add it to the list of things to be deleted
2689 when Cleanup() is called. Return the filename."""
2690 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2691 os.close(fd)
2692 OPTIONS.tempfiles.append(fn)
2693 return fn
2694
2695
Tao Bao1c830bf2017-12-25 10:43:47 -08002696def MakeTempDir(prefix='tmp', suffix=''):
2697 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2698
2699 Returns:
2700 The absolute pathname of the new directory.
2701 """
2702 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2703 OPTIONS.tempfiles.append(dir_name)
2704 return dir_name
2705
2706
Doug Zongkereef39442009-04-02 12:14:19 -07002707def Cleanup():
2708 for i in OPTIONS.tempfiles:
2709 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002710 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002711 else:
2712 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002713 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002714
2715
2716class PasswordManager(object):
2717 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002718 self.editor = os.getenv("EDITOR")
2719 self.pwfile = os.getenv("ANDROID_PW_FILE")
Tom Powell68ba8192017-01-20 20:47:49 -08002720 self.secure_storage_cmd = os.getenv("ANDROID_SECURE_STORAGE_CMD", None)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002721
2722 def GetPasswords(self, items):
2723 """Get passwords corresponding to each string in 'items',
2724 returning a dict. (The dict may have keys in addition to the
2725 values in 'items'.)
2726
2727 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2728 user edit that file to add more needed passwords. If no editor is
2729 available, or $ANDROID_PW_FILE isn't define, prompts the user
2730 interactively in the ordinary way.
2731 """
2732
2733 current = self.ReadFile()
2734
2735 first = True
2736 while True:
2737 missing = []
2738 for i in items:
2739 if i not in current or not current[i]:
Tom Powell68ba8192017-01-20 20:47:49 -08002740 # Attempt to load using ANDROID_SECURE_STORAGE_CMD
2741 if self.secure_storage_cmd:
2742 try:
2743 os.environ["TMP__KEY_FILE_NAME"] = str(i)
2744 ps = subprocess.Popen(self.secure_storage_cmd, shell=True, stdout=subprocess.PIPE)
2745 output = ps.communicate()[0]
2746 if ps.returncode == 0:
LuK133714813f12022-10-30 20:22:50 +01002747 current[i] = output.decode('utf-8')
LuK13375c8315e2022-12-04 02:07:46 +01002748 else:
2749 logger.warning('Failed to get password for key "%s".', i)
Tom Powell68ba8192017-01-20 20:47:49 -08002750 except Exception as e:
2751 print(e)
2752 pass
2753 if i not in current or not current[i]:
2754 missing.append(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002755 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002756 if not missing:
2757 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002758
2759 for i in missing:
2760 current[i] = ""
2761
2762 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002763 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002764 if sys.version_info[0] >= 3:
2765 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002766 answer = raw_input("try to edit again? [y]> ").strip()
2767 if answer and answer[0] not in 'yY':
2768 raise RuntimeError("key passwords unavailable")
2769 first = False
2770
2771 current = self.UpdateAndReadFile(current)
2772
Kelvin Zhang0876c412020-06-23 15:06:58 -04002773 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002774 """Prompt the user to enter a value (password) for each key in
2775 'current' whose value is fales. Returns a new dict with all the
2776 values.
2777 """
2778 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002779 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002780 if v:
2781 result[k] = v
2782 else:
2783 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002784 result[k] = getpass.getpass(
2785 "Enter password for %s key> " % k).strip()
2786 if result[k]:
2787 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002788 return result
2789
2790 def UpdateAndReadFile(self, current):
2791 if not self.editor or not self.pwfile:
2792 return self.PromptResult(current)
2793
2794 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002795 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002796 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2797 f.write("# (Additional spaces are harmless.)\n\n")
2798
2799 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002800 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002801 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002802 f.write("[[[ %s ]]] %s\n" % (v, k))
2803 if not v and first_line is None:
2804 # position cursor on first line with no password.
2805 first_line = i + 4
2806 f.close()
2807
Tao Bao986ee862018-10-04 15:46:16 -07002808 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002809
2810 return self.ReadFile()
2811
2812 def ReadFile(self):
2813 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002814 if self.pwfile is None:
2815 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002816 try:
2817 f = open(self.pwfile, "r")
2818 for line in f:
2819 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002820 if not line or line[0] == '#':
2821 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002822 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2823 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002824 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002825 else:
2826 result[m.group(2)] = m.group(1)
2827 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002828 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002829 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002830 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002831 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002832
2833
Dan Albert8e0178d2015-01-27 15:53:15 -08002834def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2835 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002836
2837 # http://b/18015246
2838 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2839 # for files larger than 2GiB. We can work around this by adjusting their
2840 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2841 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2842 # it isn't clear to me exactly what circumstances cause this).
2843 # `zipfile.write()` must be used directly to work around this.
2844 #
2845 # This mess can be avoided if we port to python3.
2846 saved_zip64_limit = zipfile.ZIP64_LIMIT
2847 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2848
2849 if compress_type is None:
2850 compress_type = zip_file.compression
2851 if arcname is None:
2852 arcname = filename
2853
2854 saved_stat = os.stat(filename)
2855
2856 try:
2857 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2858 # file to be zipped and reset it when we're done.
2859 os.chmod(filename, perms)
2860
2861 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002862 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2863 # intentional. zip stores datetimes in local time without a time zone
2864 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2865 # in the zip archive.
2866 local_epoch = datetime.datetime.fromtimestamp(0)
2867 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002868 os.utime(filename, (timestamp, timestamp))
2869
2870 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2871 finally:
2872 os.chmod(filename, saved_stat.st_mode)
2873 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2874 zipfile.ZIP64_LIMIT = saved_zip64_limit
2875
2876
Tao Bao58c1b962015-05-20 09:32:18 -07002877def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002878 compress_type=None):
2879 """Wrap zipfile.writestr() function to work around the zip64 limit.
2880
2881 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2882 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2883 when calling crc32(bytes).
2884
2885 But it still works fine to write a shorter string into a large zip file.
2886 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2887 when we know the string won't be too long.
2888 """
2889
2890 saved_zip64_limit = zipfile.ZIP64_LIMIT
2891 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2892
2893 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2894 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002895 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002896 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002897 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002898 else:
Tao Baof3282b42015-04-01 11:21:55 -07002899 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002900 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2901 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2902 # such a case (since
2903 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2904 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2905 # permission bits. We follow the logic in Python 3 to get consistent
2906 # behavior between using the two versions.
2907 if not zinfo.external_attr:
2908 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002909
2910 # If compress_type is given, it overrides the value in zinfo.
2911 if compress_type is not None:
2912 zinfo.compress_type = compress_type
2913
Tao Bao58c1b962015-05-20 09:32:18 -07002914 # If perms is given, it has a priority.
2915 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002916 # If perms doesn't set the file type, mark it as a regular file.
2917 if perms & 0o770000 == 0:
2918 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002919 zinfo.external_attr = perms << 16
2920
Tao Baof3282b42015-04-01 11:21:55 -07002921 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002922 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2923
Dan Albert8b72aef2015-03-23 19:13:21 -07002924 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002925 zipfile.ZIP64_LIMIT = saved_zip64_limit
2926
2927
Tao Bao89d7ab22017-12-14 17:05:33 -08002928def ZipDelete(zip_filename, entries):
2929 """Deletes entries from a ZIP file.
2930
2931 Since deleting entries from a ZIP file is not supported, it shells out to
2932 'zip -d'.
2933
2934 Args:
2935 zip_filename: The name of the ZIP file.
2936 entries: The name of the entry, or the list of names to be deleted.
2937
2938 Raises:
2939 AssertionError: In case of non-zero return from 'zip'.
2940 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002941 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002942 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002943 # If list is empty, nothing to do
2944 if not entries:
2945 return
Tao Bao89d7ab22017-12-14 17:05:33 -08002946 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002947 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002948
2949
Tao Baof3282b42015-04-01 11:21:55 -07002950def ZipClose(zip_file):
2951 # http://b/18015246
2952 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2953 # central directory.
2954 saved_zip64_limit = zipfile.ZIP64_LIMIT
2955 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2956
2957 zip_file.close()
2958
2959 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002960
2961
2962class DeviceSpecificParams(object):
2963 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002964
Doug Zongker05d3dea2009-06-22 11:32:31 -07002965 def __init__(self, **kwargs):
2966 """Keyword arguments to the constructor become attributes of this
2967 object, which is passed to all functions in the device-specific
2968 module."""
Tao Bao38884282019-07-10 22:20:56 -07002969 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002970 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002971 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002972
2973 if self.module is None:
2974 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002975 if not path:
2976 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002977 try:
2978 if os.path.isdir(path):
2979 info = imp.find_module("releasetools", [path])
2980 else:
2981 d, f = os.path.split(path)
2982 b, x = os.path.splitext(f)
2983 if x == ".py":
2984 f = b
2985 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002986 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002987 self.module = imp.load_module("device_specific", *info)
2988 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002989 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002990
2991 def _DoCall(self, function_name, *args, **kwargs):
2992 """Call the named function in the device-specific module, passing
2993 the given args and kwargs. The first argument to the call will be
2994 the DeviceSpecific object itself. If there is no module, or the
2995 module does not define the function, return the value of the
2996 'default' kwarg (which itself defaults to None)."""
2997 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002998 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002999 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
3000
3001 def FullOTA_Assertions(self):
3002 """Called after emitting the block of assertions at the top of a
3003 full OTA package. Implementations can add whatever additional
3004 assertions they like."""
3005 return self._DoCall("FullOTA_Assertions")
3006
Doug Zongkere5ff5902012-01-17 10:55:37 -08003007 def FullOTA_InstallBegin(self):
3008 """Called at the start of full OTA installation."""
3009 return self._DoCall("FullOTA_InstallBegin")
3010
Yifan Hong10c530d2018-12-27 17:34:18 -08003011 def FullOTA_GetBlockDifferences(self):
3012 """Called during full OTA installation and verification.
3013 Implementation should return a list of BlockDifference objects describing
3014 the update on each additional partitions.
3015 """
3016 return self._DoCall("FullOTA_GetBlockDifferences")
3017
Doug Zongker05d3dea2009-06-22 11:32:31 -07003018 def FullOTA_InstallEnd(self):
3019 """Called at the end of full OTA installation; typically this is
3020 used to install the image for the device's baseband processor."""
3021 return self._DoCall("FullOTA_InstallEnd")
3022
M1cha82b08a62014-11-25 15:30:48 +01003023 def FullOTA_PostValidate(self):
3024 """Called after installing and validating /system; typically this is
3025 used to resize the system partition after a block based installation."""
3026 return self._DoCall("FullOTA_PostValidate")
3027
Doug Zongker05d3dea2009-06-22 11:32:31 -07003028 def IncrementalOTA_Assertions(self):
3029 """Called after emitting the block of assertions at the top of an
3030 incremental OTA package. Implementations can add whatever
3031 additional assertions they like."""
3032 return self._DoCall("IncrementalOTA_Assertions")
3033
Doug Zongkere5ff5902012-01-17 10:55:37 -08003034 def IncrementalOTA_VerifyBegin(self):
3035 """Called at the start of the verification phase of incremental
3036 OTA installation; additional checks can be placed here to abort
3037 the script before any changes are made."""
3038 return self._DoCall("IncrementalOTA_VerifyBegin")
3039
Doug Zongker05d3dea2009-06-22 11:32:31 -07003040 def IncrementalOTA_VerifyEnd(self):
3041 """Called at the end of the verification phase of incremental OTA
3042 installation; additional checks can be placed here to abort the
3043 script before any changes are made."""
3044 return self._DoCall("IncrementalOTA_VerifyEnd")
3045
Doug Zongkere5ff5902012-01-17 10:55:37 -08003046 def IncrementalOTA_InstallBegin(self):
3047 """Called at the start of incremental OTA installation (after
3048 verification is complete)."""
3049 return self._DoCall("IncrementalOTA_InstallBegin")
3050
Yifan Hong10c530d2018-12-27 17:34:18 -08003051 def IncrementalOTA_GetBlockDifferences(self):
3052 """Called during incremental OTA installation and verification.
3053 Implementation should return a list of BlockDifference objects describing
3054 the update on each additional partitions.
3055 """
3056 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3057
Doug Zongker05d3dea2009-06-22 11:32:31 -07003058 def IncrementalOTA_InstallEnd(self):
3059 """Called at the end of incremental OTA installation; typically
3060 this is used to install the image for the device's baseband
3061 processor."""
3062 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003063
Tao Bao9bc6bb22015-11-09 16:58:28 -08003064 def VerifyOTA_Assertions(self):
3065 return self._DoCall("VerifyOTA_Assertions")
3066
Tao Bao76def242017-11-21 09:25:31 -08003067
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003068class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003069 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003070 self.name = name
3071 self.data = data
3072 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003073 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003074 self.sha1 = sha1(data).hexdigest()
3075
3076 @classmethod
3077 def FromLocalFile(cls, name, diskname):
3078 f = open(diskname, "rb")
3079 data = f.read()
3080 f.close()
3081 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003082
3083 def WriteToTemp(self):
3084 t = tempfile.NamedTemporaryFile()
3085 t.write(self.data)
3086 t.flush()
3087 return t
3088
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003089 def WriteToDir(self, d):
3090 with open(os.path.join(d, self.name), "wb") as fp:
3091 fp.write(self.data)
3092
Geremy Condra36bd3652014-02-06 19:45:10 -08003093 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003094 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003095
Tao Bao76def242017-11-21 09:25:31 -08003096
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003097DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003098 ".gz": "imgdiff",
3099 ".zip": ["imgdiff", "-z"],
3100 ".jar": ["imgdiff", "-z"],
3101 ".apk": ["imgdiff", "-z"],
3102 ".img": "imgdiff",
3103}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003104
Tao Bao76def242017-11-21 09:25:31 -08003105
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003106class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003107 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003108 self.tf = tf
3109 self.sf = sf
3110 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003111 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003112
3113 def ComputePatch(self):
3114 """Compute the patch (as a string of data) needed to turn sf into
3115 tf. Returns the same tuple as GetPatch()."""
3116
3117 tf = self.tf
3118 sf = self.sf
3119
Doug Zongker24cd2802012-08-14 16:36:15 -07003120 if self.diff_program:
3121 diff_program = self.diff_program
3122 else:
3123 ext = os.path.splitext(tf.name)[1]
3124 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003125
3126 ttemp = tf.WriteToTemp()
3127 stemp = sf.WriteToTemp()
3128
3129 ext = os.path.splitext(tf.name)[1]
3130
3131 try:
3132 ptemp = tempfile.NamedTemporaryFile()
3133 if isinstance(diff_program, list):
3134 cmd = copy.copy(diff_program)
3135 else:
3136 cmd = [diff_program]
3137 cmd.append(stemp.name)
3138 cmd.append(ttemp.name)
3139 cmd.append(ptemp.name)
3140 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003141 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003142
Doug Zongkerf8340082014-08-05 10:39:37 -07003143 def run():
3144 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003145 if e:
3146 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003147 th = threading.Thread(target=run)
3148 th.start()
3149 th.join(timeout=300) # 5 mins
3150 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003151 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003152 p.terminate()
3153 th.join(5)
3154 if th.is_alive():
3155 p.kill()
3156 th.join()
3157
Tianjie Xua2a9f992018-01-05 15:15:54 -08003158 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003159 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003160 self.patch = None
3161 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003162 diff = ptemp.read()
3163 finally:
3164 ptemp.close()
3165 stemp.close()
3166 ttemp.close()
3167
3168 self.patch = diff
3169 return self.tf, self.sf, self.patch
3170
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003171 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003172 """Returns a tuple of (target_file, source_file, patch_data).
3173
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003174 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003175 computing the patch failed.
3176 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003177 return self.tf, self.sf, self.patch
3178
3179
3180def ComputeDifferences(diffs):
3181 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003182 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003183
3184 # Do the largest files first, to try and reduce the long-pole effect.
3185 by_size = [(i.tf.size, i) for i in diffs]
3186 by_size.sort(reverse=True)
3187 by_size = [i[1] for i in by_size]
3188
3189 lock = threading.Lock()
3190 diff_iter = iter(by_size) # accessed under lock
3191
3192 def worker():
3193 try:
3194 lock.acquire()
3195 for d in diff_iter:
3196 lock.release()
3197 start = time.time()
3198 d.ComputePatch()
3199 dur = time.time() - start
3200 lock.acquire()
3201
3202 tf, sf, patch = d.GetPatch()
3203 if sf.name == tf.name:
3204 name = tf.name
3205 else:
3206 name = "%s (%s)" % (tf.name, sf.name)
3207 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003208 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003209 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003210 logger.info(
3211 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3212 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003213 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003214 except Exception:
3215 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003216 raise
3217
3218 # start worker threads; wait for them all to finish.
3219 threads = [threading.Thread(target=worker)
3220 for i in range(OPTIONS.worker_threads)]
3221 for th in threads:
3222 th.start()
3223 while threads:
3224 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003225
3226
Dan Albert8b72aef2015-03-23 19:13:21 -07003227class BlockDifference(object):
3228 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003229 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003230 self.tgt = tgt
3231 self.src = src
3232 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003233 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003234 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003235
Tao Baodd2a5892015-03-12 12:32:37 -07003236 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003237 version = max(
3238 int(i) for i in
3239 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003240 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003241 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003242
Tianjie Xu41976c72019-07-03 13:57:01 -07003243 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3244 version=self.version,
3245 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003246 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003247 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003248 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003249 self.touched_src_ranges = b.touched_src_ranges
3250 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003251
Yifan Hong10c530d2018-12-27 17:34:18 -08003252 # On devices with dynamic partitions, for new partitions,
3253 # src is None but OPTIONS.source_info_dict is not.
3254 if OPTIONS.source_info_dict is None:
3255 is_dynamic_build = OPTIONS.info_dict.get(
3256 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003257 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003258 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003259 is_dynamic_build = OPTIONS.source_info_dict.get(
3260 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003261 is_dynamic_source = partition in shlex.split(
3262 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003263
Yifan Hongbb2658d2019-01-25 12:30:58 -08003264 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003265 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3266
Yifan Hongbb2658d2019-01-25 12:30:58 -08003267 # For dynamic partitions builds, check partition list in both source
3268 # and target build because new partitions may be added, and existing
3269 # partitions may be removed.
3270 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3271
Yifan Hong10c530d2018-12-27 17:34:18 -08003272 if is_dynamic:
3273 self.device = 'map_partition("%s")' % partition
3274 else:
3275 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003276 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3277 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003278 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003279 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3280 OPTIONS.source_info_dict)
3281 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003282
Tao Baod8d14be2016-02-04 14:26:02 -08003283 @property
3284 def required_cache(self):
3285 return self._required_cache
3286
Tao Bao76def242017-11-21 09:25:31 -08003287 def WriteScript(self, script, output_zip, progress=None,
3288 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003289 if not self.src:
3290 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003291 script.Print("Patching %s image unconditionally..." % (self.partition,))
3292 else:
3293 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003294
Dan Albert8b72aef2015-03-23 19:13:21 -07003295 if progress:
3296 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003297 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003298
3299 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003300 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003301
Tao Bao9bc6bb22015-11-09 16:58:28 -08003302 def WriteStrictVerifyScript(self, script):
3303 """Verify all the blocks in the care_map, including clobbered blocks.
3304
3305 This differs from the WriteVerifyScript() function: a) it prints different
3306 error messages; b) it doesn't allow half-way updated images to pass the
3307 verification."""
3308
3309 partition = self.partition
3310 script.Print("Verifying %s..." % (partition,))
3311 ranges = self.tgt.care_map
3312 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003313 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003314 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3315 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003316 self.device, ranges_str,
3317 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003318 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003319 script.AppendExtra("")
3320
Tao Baod522bdc2016-04-12 15:53:16 -07003321 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003322 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003323
3324 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003325 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003326 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003327
3328 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003329 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003330 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003331 ranges = self.touched_src_ranges
3332 expected_sha1 = self.touched_src_sha1
3333 else:
3334 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3335 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003336
3337 # No blocks to be checked, skipping.
3338 if not ranges:
3339 return
3340
Tao Bao5ece99d2015-05-12 11:42:31 -07003341 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003342 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003343 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003344 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3345 '"%s.patch.dat")) then' % (
3346 self.device, ranges_str, expected_sha1,
3347 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003348 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003349 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003350
Tianjie Xufc3422a2015-12-15 11:53:59 -08003351 if self.version >= 4:
3352
3353 # Bug: 21124327
3354 # When generating incrementals for the system and vendor partitions in
3355 # version 4 or newer, explicitly check the first block (which contains
3356 # the superblock) of the partition to see if it's what we expect. If
3357 # this check fails, give an explicit log message about the partition
3358 # having been remounted R/W (the most likely explanation).
3359 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003360 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003361
3362 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003363 if partition == "system":
3364 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3365 else:
3366 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003367 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003368 'ifelse (block_image_recover({device}, "{ranges}") && '
3369 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003370 'package_extract_file("{partition}.transfer.list"), '
3371 '"{partition}.new.dat", "{partition}.patch.dat"), '
3372 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003373 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003374 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003375 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003376
Tao Baodd2a5892015-03-12 12:32:37 -07003377 # Abort the OTA update. Note that the incremental OTA cannot be applied
3378 # even if it may match the checksum of the target partition.
3379 # a) If version < 3, operations like move and erase will make changes
3380 # unconditionally and damage the partition.
3381 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003382 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003383 if partition == "system":
3384 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3385 else:
3386 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3387 script.AppendExtra((
3388 'abort("E%d: %s partition has unexpected contents");\n'
3389 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003390
Yifan Hong10c530d2018-12-27 17:34:18 -08003391 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003392 partition = self.partition
3393 script.Print('Verifying the updated %s image...' % (partition,))
3394 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3395 ranges = self.tgt.care_map
3396 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003397 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003398 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003399 self.device, ranges_str,
3400 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003401
3402 # Bug: 20881595
3403 # Verify that extended blocks are really zeroed out.
3404 if self.tgt.extended:
3405 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003406 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003407 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003408 self.device, ranges_str,
3409 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003410 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003411 if partition == "system":
3412 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3413 else:
3414 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003415 script.AppendExtra(
3416 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003417 ' abort("E%d: %s partition has unexpected non-zero contents after '
3418 'OTA update");\n'
3419 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003420 else:
3421 script.Print('Verified the updated %s image.' % (partition,))
3422
Tianjie Xu209db462016-05-24 17:34:52 -07003423 if partition == "system":
3424 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3425 else:
3426 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3427
Tao Bao5fcaaef2015-06-01 13:40:49 -07003428 script.AppendExtra(
3429 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003430 ' abort("E%d: %s partition has unexpected contents after OTA '
3431 'update");\n'
3432 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003433
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003434 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003435 ZipWrite(output_zip,
3436 '{}.transfer.list'.format(self.path),
3437 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003438
Tao Bao76def242017-11-21 09:25:31 -08003439 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3440 # its size. Quailty 9 almost triples the compression time but doesn't
3441 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003442 # zip | brotli(quality 6) | brotli(quality 9)
3443 # compressed_size: 942M | 869M (~8% reduced) | 854M
3444 # compression_time: 75s | 265s | 719s
3445 # decompression_time: 15s | 25s | 25s
3446
3447 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003448 brotli_cmd = ['brotli', '--quality=6',
3449 '--output={}.new.dat.br'.format(self.path),
3450 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003451 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003452 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003453
3454 new_data_name = '{}.new.dat.br'.format(self.partition)
3455 ZipWrite(output_zip,
3456 '{}.new.dat.br'.format(self.path),
3457 new_data_name,
3458 compress_type=zipfile.ZIP_STORED)
3459 else:
3460 new_data_name = '{}.new.dat'.format(self.partition)
3461 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3462
Dan Albert8e0178d2015-01-27 15:53:15 -08003463 ZipWrite(output_zip,
3464 '{}.patch.dat'.format(self.path),
3465 '{}.patch.dat'.format(self.partition),
3466 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003467
Tianjie Xu209db462016-05-24 17:34:52 -07003468 if self.partition == "system":
3469 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3470 else:
3471 code = ErrorCode.VENDOR_UPDATE_FAILURE
3472
Yifan Hong10c530d2018-12-27 17:34:18 -08003473 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003474 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003475 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003476 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003477 device=self.device, partition=self.partition,
3478 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003479 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003480
Kelvin Zhang0876c412020-06-23 15:06:58 -04003481 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003482 data = source.ReadRangeSet(ranges)
3483 ctx = sha1()
3484
3485 for p in data:
3486 ctx.update(p)
3487
3488 return ctx.hexdigest()
3489
Kelvin Zhang0876c412020-06-23 15:06:58 -04003490 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003491 """Return the hash value for all zero blocks."""
3492 zero_block = '\x00' * 4096
3493 ctx = sha1()
3494 for _ in range(num_blocks):
3495 ctx.update(zero_block)
3496
3497 return ctx.hexdigest()
3498
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003499
Tianjie Xu41976c72019-07-03 13:57:01 -07003500# Expose these two classes to support vendor-specific scripts
3501DataImage = images.DataImage
3502EmptyImage = images.EmptyImage
3503
Tao Bao76def242017-11-21 09:25:31 -08003504
Doug Zongker96a57e72010-09-26 14:57:41 -07003505# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003506PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003507 "ext4": "EMMC",
3508 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003509 "f2fs": "EMMC",
Tim Zimmermannad19ca52022-10-01 11:56:57 +02003510 "squashfs": "EMMC",
3511 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003512}
Doug Zongker96a57e72010-09-26 14:57:41 -07003513
Kelvin Zhang0876c412020-06-23 15:06:58 -04003514
Yifan Hongbdb32012020-05-07 12:38:53 -07003515def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3516 """
3517 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3518 backwards compatibility. It aborts if the fstab entry has slotselect option
3519 (unless check_no_slot is explicitly set to False).
3520 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003521 fstab = info["fstab"]
3522 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003523 if check_no_slot:
3524 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003525 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003526 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3527 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003528 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003529
3530
Yifan Hongbdb32012020-05-07 12:38:53 -07003531def GetTypeAndDeviceExpr(mount_point, info):
3532 """
3533 Return the filesystem of the partition, and an edify expression that evaluates
3534 to the device at runtime.
3535 """
3536 fstab = info["fstab"]
3537 if fstab:
3538 p = fstab[mount_point]
3539 device_expr = '"%s"' % fstab[mount_point].device
3540 if p.slotselect:
3541 device_expr = 'add_slot_suffix(%s)' % device_expr
3542 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003543 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003544
3545
3546def GetEntryForDevice(fstab, device):
3547 """
3548 Returns:
3549 The first entry in fstab whose device is the given value.
3550 """
3551 if not fstab:
3552 return None
3553 for mount_point in fstab:
3554 if fstab[mount_point].device == device:
3555 return fstab[mount_point]
3556 return None
3557
Kelvin Zhang0876c412020-06-23 15:06:58 -04003558
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003559def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003560 """Parses and converts a PEM-encoded certificate into DER-encoded.
3561
3562 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3563
3564 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003565 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003566 """
3567 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003568 save = False
3569 for line in data.split("\n"):
3570 if "--END CERTIFICATE--" in line:
3571 break
3572 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003573 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003574 if "--BEGIN CERTIFICATE--" in line:
3575 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003576 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003577 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003578
Tao Bao04e1f012018-02-04 12:13:35 -08003579
3580def ExtractPublicKey(cert):
3581 """Extracts the public key (PEM-encoded) from the given certificate file.
3582
3583 Args:
3584 cert: The certificate filename.
3585
3586 Returns:
3587 The public key string.
3588
3589 Raises:
3590 AssertionError: On non-zero return from 'openssl'.
3591 """
3592 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3593 # While openssl 1.1 writes the key into the given filename followed by '-out',
3594 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3595 # stdout instead.
3596 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3597 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3598 pubkey, stderrdata = proc.communicate()
3599 assert proc.returncode == 0, \
3600 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3601 return pubkey
3602
3603
Tao Bao1ac886e2019-06-26 11:58:22 -07003604def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003605 """Extracts the AVB public key from the given public or private key.
3606
3607 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003608 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003609 key: The input key file, which should be PEM-encoded public or private key.
3610
3611 Returns:
3612 The path to the extracted AVB public key file.
3613 """
3614 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3615 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003616 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003617 return output
3618
3619
Doug Zongker412c02f2014-02-13 10:58:24 -08003620def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3621 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003622 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003623
Tao Bao6d5d6232018-03-09 17:04:42 -08003624 Most of the space in the boot and recovery images is just the kernel, which is
3625 identical for the two, so the resulting patch should be efficient. Add it to
3626 the output zip, along with a shell script that is run from init.rc on first
3627 boot to actually do the patching and install the new recovery image.
3628
3629 Args:
3630 input_dir: The top-level input directory of the target-files.zip.
3631 output_sink: The callback function that writes the result.
3632 recovery_img: File object for the recovery image.
3633 boot_img: File objects for the boot image.
3634 info_dict: A dict returned by common.LoadInfoDict() on the input
3635 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003636 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003637 if info_dict is None:
3638 info_dict = OPTIONS.info_dict
3639
Tao Bao6d5d6232018-03-09 17:04:42 -08003640 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003641 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
Ricky Cheung7264f022024-03-29 18:55:05 +08003642 board_builds_vendorimage = info_dict.get("board_builds_vendorimage") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003643
Ricky Cheung7264f022024-03-29 18:55:05 +08003644 recovery_img_path = "etc/recovery.img"
3645 if board_builds_vendorimage:
Bill Peckhame868aec2019-09-17 17:06:47 -07003646 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
Ricky Cheung7264f022024-03-29 18:55:05 +08003647 elif not board_uses_vendorimage:
Bill Peckhame868aec2019-09-17 17:06:47 -07003648 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
Ricky Cheung7264f022024-03-29 18:55:05 +08003649 else:
3650 logger.warning('Recovery patch generation is disable when prebuilt vendor image is used.')
3651 return None
Doug Zongkerc9253822014-02-04 12:17:58 -08003652
Tao Baof2cffbd2015-07-22 12:33:18 -07003653 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003654 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003655
3656 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003657 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003658 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003659 # With system-root-image, boot and recovery images will have mismatching
3660 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3661 # to handle such a case.
3662 if system_root_image:
3663 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003664 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003665 assert not os.path.exists(path)
3666 else:
3667 diff_program = ["imgdiff"]
3668 if os.path.exists(path):
3669 diff_program.append("-b")
3670 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003671 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003672 else:
3673 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003674
3675 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3676 _, _, patch = d.ComputePatch()
3677 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003678
Dan Albertebb19aa2015-03-27 19:11:53 -07003679 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003680 # The following GetTypeAndDevice()s need to use the path in the target
3681 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003682 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3683 check_no_slot=False)
3684 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3685 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003686 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003687 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003688
Tao Baof2cffbd2015-07-22 12:33:18 -07003689 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003690
3691 # Note that we use /vendor to refer to the recovery resources. This will
3692 # work for a separate vendor partition mounted at /vendor or a
3693 # /system/vendor subdirectory on the system partition, for which init will
3694 # create a symlink from /vendor to /system/vendor.
3695
3696 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003697if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3698 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003699 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003700 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3701 log -t recovery "Installing new recovery image: succeeded" || \\
3702 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003703else
3704 log -t recovery "Recovery image already installed"
3705fi
3706""" % {'type': recovery_type,
3707 'device': recovery_device,
3708 'sha1': recovery_img.sha1,
3709 'size': recovery_img.size}
3710 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003711 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003712if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3713 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003714 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003715 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3716 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3717 log -t recovery "Installing new recovery image: succeeded" || \\
3718 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003719else
3720 log -t recovery "Recovery image already installed"
3721fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003722""" % {'boot_size': boot_img.size,
3723 'boot_sha1': boot_img.sha1,
3724 'recovery_size': recovery_img.size,
3725 'recovery_sha1': recovery_img.sha1,
3726 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003727 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003728 'recovery_type': recovery_type,
3729 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003730 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003731
Bill Peckhame868aec2019-09-17 17:06:47 -07003732 # The install script location moved from /system/etc to /system/bin in the L
3733 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
dianlujitaoad6e17c2020-09-12 13:48:26 +08003734 output_sink("bin/install-recovery.sh", sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003735
3736
3737class DynamicPartitionUpdate(object):
3738 def __init__(self, src_group=None, tgt_group=None, progress=None,
3739 block_difference=None):
3740 self.src_group = src_group
3741 self.tgt_group = tgt_group
3742 self.progress = progress
3743 self.block_difference = block_difference
3744
3745 @property
3746 def src_size(self):
3747 if not self.block_difference:
3748 return 0
3749 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3750
3751 @property
3752 def tgt_size(self):
3753 if not self.block_difference:
3754 return 0
3755 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3756
3757 @staticmethod
3758 def _GetSparseImageSize(img):
3759 if not img:
3760 return 0
3761 return img.blocksize * img.total_blocks
3762
3763
3764class DynamicGroupUpdate(object):
3765 def __init__(self, src_size=None, tgt_size=None):
3766 # None: group does not exist. 0: no size limits.
3767 self.src_size = src_size
3768 self.tgt_size = tgt_size
3769
3770
3771class DynamicPartitionsDifference(object):
3772 def __init__(self, info_dict, block_diffs, progress_dict=None,
Peter Cai6001dfb2020-03-01 14:43:57 +08003773 source_info_dict=None, build_without_vendor=False):
Yifan Hong10c530d2018-12-27 17:34:18 -08003774 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003775 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003776
Peter Cai6001dfb2020-03-01 14:43:57 +08003777 self._build_without_vendor = build_without_vendor
Yifan Hong10c530d2018-12-27 17:34:18 -08003778 self._remove_all_before_apply = False
3779 if source_info_dict is None:
3780 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003781 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003782
Tao Baof1113e92019-06-18 12:10:14 -07003783 block_diff_dict = collections.OrderedDict(
3784 [(e.partition, e) for e in block_diffs])
3785
Yifan Hong10c530d2018-12-27 17:34:18 -08003786 assert len(block_diff_dict) == len(block_diffs), \
3787 "Duplicated BlockDifference object for {}".format(
3788 [partition for partition, count in
3789 collections.Counter(e.partition for e in block_diffs).items()
3790 if count > 1])
3791
Yifan Hong79997e52019-01-23 16:56:19 -08003792 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003793
3794 for p, block_diff in block_diff_dict.items():
3795 self._partition_updates[p] = DynamicPartitionUpdate()
3796 self._partition_updates[p].block_difference = block_diff
3797
3798 for p, progress in progress_dict.items():
3799 if p in self._partition_updates:
3800 self._partition_updates[p].progress = progress
3801
3802 tgt_groups = shlex.split(info_dict.get(
3803 "super_partition_groups", "").strip())
3804 src_groups = shlex.split(source_info_dict.get(
3805 "super_partition_groups", "").strip())
3806
3807 for g in tgt_groups:
3808 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003809 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003810 assert p in self._partition_updates, \
3811 "{} is in target super_{}_partition_list but no BlockDifference " \
3812 "object is provided.".format(p, g)
3813 self._partition_updates[p].tgt_group = g
3814
3815 for g in src_groups:
3816 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003817 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003818 assert p in self._partition_updates, \
3819 "{} is in source super_{}_partition_list but no BlockDifference " \
3820 "object is provided.".format(p, g)
3821 self._partition_updates[p].src_group = g
3822
Yifan Hong45433e42019-01-18 13:55:25 -08003823 target_dynamic_partitions = set(shlex.split(info_dict.get(
3824 "dynamic_partition_list", "").strip()))
3825 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3826 if u.tgt_size)
3827 assert block_diffs_with_target == target_dynamic_partitions, \
3828 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3829 list(target_dynamic_partitions), list(block_diffs_with_target))
3830
3831 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3832 "dynamic_partition_list", "").strip()))
3833 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3834 if u.src_size)
3835 assert block_diffs_with_source == source_dynamic_partitions, \
3836 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3837 list(source_dynamic_partitions), list(block_diffs_with_source))
3838
Yifan Hong10c530d2018-12-27 17:34:18 -08003839 if self._partition_updates:
3840 logger.info("Updating dynamic partitions %s",
3841 self._partition_updates.keys())
3842
Yifan Hong79997e52019-01-23 16:56:19 -08003843 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003844
3845 for g in tgt_groups:
3846 self._group_updates[g] = DynamicGroupUpdate()
3847 self._group_updates[g].tgt_size = int(info_dict.get(
3848 "super_%s_group_size" % g, "0").strip())
3849
3850 for g in src_groups:
3851 if g not in self._group_updates:
3852 self._group_updates[g] = DynamicGroupUpdate()
3853 self._group_updates[g].src_size = int(source_info_dict.get(
3854 "super_%s_group_size" % g, "0").strip())
3855
3856 self._Compute()
3857
3858 def WriteScript(self, script, output_zip, write_verify_script=False):
3859 script.Comment('--- Start patching dynamic partitions ---')
3860 for p, u in self._partition_updates.items():
3861 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3862 script.Comment('Patch partition %s' % p)
3863 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3864 write_verify_script=False)
3865
3866 op_list_path = MakeTempFile()
3867 with open(op_list_path, 'w') as f:
3868 for line in self._op_list:
3869 f.write('{}\n'.format(line))
3870
3871 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3872
3873 script.Comment('Update dynamic partition metadata')
Alexander Martinz9004c0d2024-08-19 08:47:08 +02003874 script.AppendExtra('assert(update_dynamic_partitions('
3875 'package_extract_file("dynamic_partitions_op_list")));')
Yifan Hong10c530d2018-12-27 17:34:18 -08003876
3877 if write_verify_script:
3878 for p, u in self._partition_updates.items():
3879 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3880 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003881 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003882
3883 for p, u in self._partition_updates.items():
3884 if u.tgt_size and u.src_size <= u.tgt_size:
3885 script.Comment('Patch partition %s' % p)
3886 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3887 write_verify_script=write_verify_script)
3888 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003889 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003890
3891 script.Comment('--- End patching dynamic partitions ---')
3892
3893 def _Compute(self):
3894 self._op_list = list()
3895
3896 def append(line):
3897 self._op_list.append(line)
3898
3899 def comment(line):
3900 self._op_list.append("# %s" % line)
3901
Peter Cai6001dfb2020-03-01 14:43:57 +08003902 if self._build_without_vendor:
3903 comment('System-only build, keep original vendor partition')
3904 # When building without vendor, we do not want to override
3905 # any partition already existing. In this case, we can only
3906 # resize, but not remove / create / re-create any other
3907 # partition.
3908 for p, u in self._partition_updates.items():
3909 comment('Resize partition %s to %s' % (p, u.tgt_size))
3910 append('resize %s %s' % (p, u.tgt_size))
3911 return
3912
Yifan Hong10c530d2018-12-27 17:34:18 -08003913 if self._remove_all_before_apply:
3914 comment('Remove all existing dynamic partitions and groups before '
3915 'applying full OTA')
3916 append('remove_all_groups')
3917
3918 for p, u in self._partition_updates.items():
3919 if u.src_group and not u.tgt_group:
3920 append('remove %s' % p)
3921
3922 for p, u in self._partition_updates.items():
3923 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3924 comment('Move partition %s from %s to default' % (p, u.src_group))
3925 append('move %s default' % p)
3926
3927 for p, u in self._partition_updates.items():
3928 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3929 comment('Shrink partition %s from %d to %d' %
3930 (p, u.src_size, u.tgt_size))
3931 append('resize %s %s' % (p, u.tgt_size))
3932
3933 for g, u in self._group_updates.items():
3934 if u.src_size is not None and u.tgt_size is None:
3935 append('remove_group %s' % g)
3936 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003937 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003938 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3939 append('resize_group %s %d' % (g, u.tgt_size))
3940
3941 for g, u in self._group_updates.items():
3942 if u.src_size is None and u.tgt_size is not None:
3943 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3944 append('add_group %s %d' % (g, u.tgt_size))
3945 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003946 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003947 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3948 append('resize_group %s %d' % (g, u.tgt_size))
3949
3950 for p, u in self._partition_updates.items():
3951 if u.tgt_group and not u.src_group:
3952 comment('Add partition %s to group %s' % (p, u.tgt_group))
3953 append('add %s %s' % (p, u.tgt_group))
3954
3955 for p, u in self._partition_updates.items():
3956 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003957 comment('Grow partition %s from %d to %d' %
3958 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003959 append('resize %s %d' % (p, u.tgt_size))
3960
3961 for p, u in self._partition_updates.items():
3962 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3963 comment('Move partition %s from default to %s' %
3964 (p, u.tgt_group))
3965 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003966
3967
jiajia tangf3f842b2021-03-17 21:49:44 +08003968def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003969 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003970 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003971
3972 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003973 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003974
3975 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003976 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003977 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003978 tmp_dir = MakeTempDir('boot_', suffix='.img')
3979 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003980 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3981 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003982 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3983 if not os.path.isfile(ramdisk):
3984 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3985 return None
3986 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003987 if ramdisk_format == RamdiskFormat.LZ4:
3988 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3989 elif ramdisk_format == RamdiskFormat.GZ:
3990 with open(ramdisk, 'rb') as input_stream:
3991 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003992 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3993 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003994 p2.wait()
Luca Stefani7f2913c2020-06-11 13:03:18 +02003995 elif ramdisk_format == RamdiskFormat.XZ:
3996 with open(ramdisk, 'rb') as input_stream:
3997 with open(uncompressed_ramdisk, 'wb') as output_stream:
3998 p2 = Run(['xz', '-d'], stdin=input_stream.fileno(),
3999 stdout=output_stream.fileno())
4000 p2.wait()
jiajia tangf3f842b2021-03-17 21:49:44 +08004001 else:
Luca Stefani7f2913c2020-06-11 13:03:18 +02004002 logger.error('Only support lz4, xz, or minigzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08004003 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004004
4005 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
4006 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
4007 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
4008 # the host environment.
4009 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04004010 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08004011
Yifan Hongc65a0542021-01-07 14:21:01 -08004012 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
4013 prop_file = os.path.join(extracted_ramdisk, search_path)
4014 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08004015 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04004016 logger.warning(
4017 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08004018
Yifan Hong7dc51172021-01-12 11:27:39 -08004019 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004020
Yifan Hong85ac5012021-01-07 14:43:46 -08004021 except ExternalError as e:
4022 logger.warning('Unable to get boot image build props: %s', e)
4023 return None
4024
4025
4026def GetBootImageTimestamp(boot_img):
4027 """
4028 Get timestamp from ramdisk within the boot image
4029
4030 Args:
4031 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
4032
4033 Return:
4034 An integer that corresponds to the timestamp of the boot image, or None
4035 if file has unknown format. Raise exception if an unexpected error has
4036 occurred.
4037 """
4038 prop_file = GetBootImageBuildProp(boot_img)
4039 if not prop_file:
4040 return None
4041
4042 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4043 if props is None:
4044 return None
4045
4046 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004047 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4048 if timestamp:
4049 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004050 logger.warning(
4051 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004052 return None
4053
4054 except ExternalError as e:
4055 logger.warning('Unable to get boot image timestamp: %s', e)
4056 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004057
4058
4059def GetCareMap(which, imgname):
4060 """Returns the care_map string for the given partition.
4061
4062 Args:
4063 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
4064 imgname: The filename of the image.
4065
4066 Returns:
4067 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
4068 RangeSet; or None.
4069 """
4070 assert which in PARTITIONS_WITH_CARE_MAP
4071
4072 # which + "_image_size" contains the size that the actual filesystem image
4073 # resides in, which is all that needs to be verified. The additional blocks in
4074 # the image file contain verity metadata, by reading which would trigger
4075 # invalid reads.
4076 image_size = OPTIONS.info_dict.get(which + "_image_size")
4077 if not image_size:
4078 return None
4079
David Anderson9e95a022021-08-31 21:32:45 -07004080 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
4081
Kelvin Zhang27324132021-03-22 15:38:38 -04004082 image_blocks = int(image_size) // 4096 - 1
Kelvin Zhang98ef7bb2022-01-07 14:41:46 -08004083 # It's OK for image_blocks to be 0, because care map ranges are inclusive.
4084 # So 0-0 means "just block 0", which is valid.
4085 assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
4086 which, image_size)
Kelvin Zhang27324132021-03-22 15:38:38 -04004087
4088 # For sparse images, we will only check the blocks that are listed in the care
4089 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07004090 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04004091 simg = sparse_img.SparseImage(imgname)
4092 care_map_ranges = simg.care_map.intersect(
4093 rangelib.RangeSet("0-{}".format(image_blocks)))
4094
4095 # Otherwise for non-sparse images, we read all the blocks in the filesystem
4096 # image.
4097 else:
4098 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
4099
4100 return [which, care_map_ranges.to_string_raw()]
4101
4102
4103def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
4104 """Generates and adds care_map.pb for a/b partition that has care_map.
4105
4106 Args:
4107 output_file: The output zip file (needs to be already open),
4108 or file path to write care_map.pb.
4109 ab_partitions: The list of A/B partitions.
4110 image_paths: A map from the partition name to the image path.
4111 """
4112 if not output_file:
4113 raise ExternalError('Expected output_file for AddCareMapForAbOta')
4114
4115 care_map_list = []
4116 for partition in ab_partitions:
4117 partition = partition.strip()
4118 if partition not in PARTITIONS_WITH_CARE_MAP:
4119 continue
4120
4121 verity_block_device = "{}_verity_block_device".format(partition)
4122 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
4123 if (verity_block_device in OPTIONS.info_dict or
4124 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
4125 if partition not in image_paths:
4126 logger.warning('Potential partition with care_map missing from images: %s',
4127 partition)
4128 continue
4129 image_path = image_paths[partition]
4130 if not os.path.exists(image_path):
4131 raise ExternalError('Expected image at path {}'.format(image_path))
4132
4133 care_map = GetCareMap(partition, image_path)
4134 if not care_map:
4135 continue
4136 care_map_list += care_map
4137
4138 # adds fingerprint field to the care_map
4139 # TODO(xunchang) revisit the fingerprint calculation for care_map.
4140 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
4141 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
4142 "ro.{}.build.thumbprint".format(partition)]
4143
4144 present_props = [x for x in prop_name_list if
4145 partition_props and partition_props.GetProp(x)]
4146 if not present_props:
4147 logger.warning(
4148 "fingerprint is not present for partition %s", partition)
4149 property_id, fingerprint = "unknown", "unknown"
4150 else:
4151 property_id = present_props[0]
4152 fingerprint = partition_props.GetProp(property_id)
4153 care_map_list += [property_id, fingerprint]
4154
4155 if not care_map_list:
4156 return
4157
4158 # Converts the list into proto buf message by calling care_map_generator; and
4159 # writes the result to a temp file.
4160 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
4161 suffix=".txt")
4162 with open(temp_care_map_text, 'w') as text_file:
4163 text_file.write('\n'.join(care_map_list))
4164
4165 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
4166 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
4167 RunAndCheckOutput(care_map_gen_cmd)
4168
4169 if not isinstance(output_file, zipfile.ZipFile):
4170 shutil.copy(temp_care_map, output_file)
4171 return
4172 # output_file is a zip file
4173 care_map_path = "META/care_map.pb"
4174 if care_map_path in output_file.namelist():
4175 # Copy the temp file into the OPTIONS.input_tmp dir and update the
4176 # replace_updated_files_list used by add_img_to_target_files
4177 if not OPTIONS.replace_updated_files_list:
4178 OPTIONS.replace_updated_files_list = []
4179 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
4180 OPTIONS.replace_updated_files_list.append(care_map_path)
4181 else:
4182 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07004183
4184
4185def IsSparseImage(filepath):
4186 with open(filepath, 'rb') as fp:
4187 # Magic for android sparse image format
4188 # https://source.android.com/devices/bootloader/images
4189 return fp.read(4) == b'\x3A\xFF\x26\xED'