blob: d97611cbdd0b46c6c98daa71c90aa1237ff60928 [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040019import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Tao Bao32fcdab2018-10-12 10:30:39 -070025import json
26import logging
27import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070028import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080029import platform
Doug Zongkereef39442009-04-02 12:14:19 -070030import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070031import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070032import shutil
33import subprocess
Kelvin Zhange473ce92023-06-21 13:06:59 -070034import stat
Dennis Song6e5e44d2023-10-03 02:18:06 +000035import sys
Doug Zongkereef39442009-04-02 12:14:19 -070036import tempfile
Doug Zongker048e7ca2009-06-15 14:31:53 -070037import zipfile
Dennis Song4aae62e2023-10-02 04:31:34 +000038from dataclasses import dataclass
Tao Bao12d87fc2018-01-31 12:18:52 -080039from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070040
Tianjie Xu41976c72019-07-03 13:57:01 -070041import images
Tao Baoc765cca2018-01-31 17:32:40 -080042import sparse_img
Kelvin Zhang513b86e2023-10-27 13:27:07 -070043
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070051 # Set up search path, in order to find framework/ and lib64/. At the time of
52 # running this function, user-supplied search path (`--path`) hasn't been
53 # available. So the value set here is the default, which might be overridden
54 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040055 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070056 if exec_path.endswith('.py'):
57 script_name = os.path.basename(exec_path)
58 # logger hasn't been initialized yet at this point. Use print to output
59 # warnings.
60 print(
61 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040062 'executable -- build and run `{}` directly.'.format(
63 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070064 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080068 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
69 if "ANDROID_HOST_OUT" in os.environ:
70 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080071 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.extra_signapk_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000073 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070074 self.java_path = "java" # Use the one on the path by default.
Sorin Basca05085832022-09-14 11:33:22 +010075 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080076 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.public_key_suffix = ".x509.pem"
78 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070079 # use otatools built boot_signer by default
Dan Albert8b72aef2015-03-23 19:13:21 -070080 self.verbose = False
81 self.tempfiles = []
82 self.device_specific = None
83 self.extras = {}
84 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070085 self.source_info_dict = None
86 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070088 # Stash size cannot exceed cache_size * threshold.
89 self.cache_size = None
90 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070091 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070092
93
94OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070095
Tao Bao71197512018-10-11 14:08:45 -070096# The block size that's used across the releasetools scripts.
97BLOCK_SIZE = 4096
98
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080099# Values for "certificate" in apkcerts that mean special things.
100SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
101
Tao Bao5cc0abb2019-03-21 10:18:05 -0700102# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
103# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800104# descriptor into vbmeta.img. When adding a new entry here, the
105# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
106# accordingly.
Dennis Song6e5e44d2023-10-03 02:18:06 +0000107AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw',
108 'recovery', 'system', 'system_ext', 'vendor', 'vendor_boot',
109 'vendor_kernel_boot', 'vendor_dlkm', 'odm_dlkm',
110 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800111
Tao Bao08c190f2019-06-03 23:07:58 -0700112# Chained VBMeta partitions.
113AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
114
Dennis Song6e5e44d2023-10-03 02:18:06 +0000115# avbtool arguments name
116AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG = '--include_descriptors_from_image'
117AVB_ARG_NAME_CHAIN_PARTITION = '--chain_partition'
118
Tianjie Xu861f4132018-09-12 11:49:33 -0700119# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400120PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700121 'system',
122 'vendor',
123 'product',
124 'system_ext',
125 'odm',
126 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700127 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000128 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hong5057b952021-01-07 14:09:57 -0800131# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000132PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800133
Yifan Hongc65a0542021-01-07 14:21:01 -0800134# See sysprop.mk. If file is moved, add new search paths here; don't remove
135# existing search paths.
136RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700137
Kelvin Zhang563750f2021-04-28 12:46:17 -0400138
Dennis Song4aae62e2023-10-02 04:31:34 +0000139@dataclass
140class AvbChainedPartitionArg:
141 """The required arguments for avbtool --chain_partition."""
142 partition: str
143 rollback_index_location: int
144 pubkey_path: str
145
146 def to_string(self):
147 """Convert to string command arguments."""
148 return '{}:{}:{}'.format(
149 self.partition, self.rollback_index_location, self.pubkey_path)
150
151
Dan Albert8b72aef2015-03-23 19:13:21 -0700152class ExternalError(RuntimeError):
153 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700154
155
Tao Bao32fcdab2018-10-12 10:30:39 -0700156def InitLogging():
157 DEFAULT_LOGGING_CONFIG = {
158 'version': 1,
159 'disable_existing_loggers': False,
160 'formatters': {
161 'standard': {
162 'format':
163 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
164 'datefmt': '%Y-%m-%d %H:%M:%S',
165 },
166 },
167 'handlers': {
168 'default': {
169 'class': 'logging.StreamHandler',
170 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700171 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700172 },
173 },
174 'loggers': {
175 '': {
176 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700177 'propagate': True,
Kelvin Zhang9d741282023-10-24 15:35:54 -0700178 'level': 'NOTSET',
Tao Bao32fcdab2018-10-12 10:30:39 -0700179 }
180 }
181 }
182 env_config = os.getenv('LOGGING_CONFIG')
183 if env_config:
184 with open(env_config) as f:
185 config = json.load(f)
186 else:
187 config = DEFAULT_LOGGING_CONFIG
188
189 # Increase the logging level for verbose mode.
190 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700191 config = copy.deepcopy(config)
192 config['handlers']['default']['level'] = 'INFO'
193
194 if OPTIONS.logfile:
195 config = copy.deepcopy(config)
196 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400197 'class': 'logging.FileHandler',
198 'formatter': 'standard',
199 'level': 'INFO',
200 'mode': 'w',
201 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700202 }
203 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700204
205 logging.config.dictConfig(config)
206
207
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900208def FindHostToolPath(tool_name):
209 """Finds the path to the host tool.
210
211 Args:
212 tool_name: name of the tool to find
213 Returns:
Cole Faust6833d7d2023-08-01 18:00:37 -0700214 path to the tool if found under the same directory as this binary is located at. If not found,
215 tool_name is returned.
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900216 """
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900217 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
218 tool_path = os.path.join(my_dir, tool_name)
219 if os.path.exists(tool_path):
220 return tool_path
221
222 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700223
Kelvin Zhang563750f2021-04-28 12:46:17 -0400224
Tao Bao39451582017-05-04 11:10:47 -0700225def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700226 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700227
Tao Bao73dd4f42018-10-04 16:25:33 -0700228 Args:
229 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700230 verbose: Whether the commands should be shown. Default to the global
231 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700232 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
233 stdin, etc. stdout and stderr will default to subprocess.PIPE and
234 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800235 universal_newlines will default to True, as most of the users in
236 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700237
238 Returns:
239 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700240 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700241 if 'stdout' not in kwargs and 'stderr' not in kwargs:
242 kwargs['stdout'] = subprocess.PIPE
243 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800244 if 'universal_newlines' not in kwargs:
245 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700246
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900247 if args:
248 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700249 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900250 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700251
Kelvin Zhang766eea72021-06-03 09:36:08 -0400252 if verbose is None:
253 verbose = OPTIONS.verbose
254
Tao Bao32fcdab2018-10-12 10:30:39 -0700255 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400256 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700258 return subprocess.Popen(args, **kwargs)
259
260
Tao Bao986ee862018-10-04 15:46:16 -0700261def RunAndCheckOutput(args, verbose=None, **kwargs):
262 """Runs the given command and returns the output.
263
264 Args:
265 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700266 verbose: Whether the commands should be shown. Default to the global
267 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700268 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
269 stdin, etc. stdout and stderr will default to subprocess.PIPE and
270 subprocess.STDOUT respectively unless caller specifies any of them.
271
272 Returns:
273 The output string.
274
275 Raises:
276 ExternalError: On non-zero exit from the command.
277 """
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -0800278 if verbose is None:
279 verbose = OPTIONS.verbose
Tao Bao986ee862018-10-04 15:46:16 -0700280 proc = Run(args, verbose=verbose, **kwargs)
281 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800282 if output is None:
283 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400285 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700286 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700287 if proc.returncode != 0:
288 raise ExternalError(
289 "Failed to run command '{}' (exit code {}):\n{}".format(
290 args, proc.returncode, output))
291 return output
292
293
Tao Baoc765cca2018-01-31 17:32:40 -0800294def RoundUpTo4K(value):
295 rounded_up = value + 4095
296 return rounded_up - (rounded_up % 4096)
297
298
Ying Wang7e6d4e42010-12-13 16:25:36 -0800299def CloseInheritedPipes():
300 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
301 before doing other work."""
302 if platform.system() != "Darwin":
303 return
304 for d in range(3, 1025):
305 try:
306 stat = os.fstat(d)
307 if stat is not None:
308 pipebit = stat[0] & 0x1000
309 if pipebit != 0:
310 os.close(d)
311 except OSError:
312 pass
313
314
Tao Bao1c320f82019-10-04 23:25:12 -0700315class BuildInfo(object):
316 """A class that holds the information for a given build.
317
318 This class wraps up the property querying for a given source or target build.
319 It abstracts away the logic of handling OEM-specific properties, and caches
320 the commonly used properties such as fingerprint.
321
322 There are two types of info dicts: a) build-time info dict, which is generated
323 at build time (i.e. included in a target_files zip); b) OEM info dict that is
324 specified at package generation time (via command line argument
325 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
326 having "oem_fingerprint_properties" in build-time info dict), all the queries
327 would be answered based on build-time info dict only. Otherwise if using
328 OEM-specific properties, some of them will be calculated from two info dicts.
329
330 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800331 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700332
333 Attributes:
334 info_dict: The build-time info dict.
335 is_ab: Whether it's a build that uses A/B OTA.
336 oem_dicts: A list of OEM dicts.
337 oem_props: A list of OEM properties that should be read from OEM dicts; None
338 if the build doesn't use any OEM-specific property.
339 fingerprint: The fingerprint of the build, which would be calculated based
340 on OEM properties if applicable.
341 device: The device name, which could come from OEM dicts if applicable.
342 """
343
344 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
345 "ro.product.manufacturer", "ro.product.model",
346 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700347 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
348 "product", "odm", "vendor", "system_ext", "system"]
349 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
350 "product", "product_services", "odm", "vendor", "system"]
351 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700352
Tianjiefdda51d2021-05-05 14:46:35 -0700353 # The length of vbmeta digest to append to the fingerprint
354 _VBMETA_DIGEST_SIZE_USED = 8
355
356 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700357 """Initializes a BuildInfo instance with the given dicts.
358
359 Note that it only wraps up the given dicts, without making copies.
360
361 Arguments:
362 info_dict: The build-time info dict.
363 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
364 that it always uses the first dict to calculate the fingerprint or the
365 device name. The rest would be used for asserting OEM properties only
366 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700367 use_legacy_id: Use the legacy build id to construct the fingerprint. This
368 is used when we need a BuildInfo class, while the vbmeta digest is
369 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700370
371 Raises:
372 ValueError: On invalid inputs.
373 """
374 self.info_dict = info_dict
375 self.oem_dicts = oem_dicts
376
377 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700378 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700379
Hongguang Chend7c160f2020-05-03 21:24:26 -0700380 # Skip _oem_props if oem_dicts is None to use BuildInfo in
381 # sign_target_files_apks
382 if self.oem_dicts:
383 self._oem_props = info_dict.get("oem_fingerprint_properties")
384 else:
385 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700386
Daniel Normand5fe8622020-01-08 17:01:11 -0800387 def check_fingerprint(fingerprint):
388 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
389 raise ValueError(
390 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
391 "3.2.2. Build Parameters.".format(fingerprint))
392
Daniel Normand5fe8622020-01-08 17:01:11 -0800393 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800394 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800395 try:
396 fingerprint = self.CalculatePartitionFingerprint(partition)
397 check_fingerprint(fingerprint)
398 self._partition_fingerprints[partition] = fingerprint
399 except ExternalError:
400 continue
401 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800402 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800403 # need a fingerprint when creating the image.
404 self._partition_fingerprints[
405 "system_other"] = self._partition_fingerprints["system"]
406
Tao Bao1c320f82019-10-04 23:25:12 -0700407 # These two should be computed only after setting self._oem_props.
408 self._device = self.GetOemProperty("ro.product.device")
409 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800410 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700411
412 @property
413 def is_ab(self):
414 return self._is_ab
415
416 @property
417 def device(self):
418 return self._device
419
420 @property
421 def fingerprint(self):
422 return self._fingerprint
423
424 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400425 def is_vabc(self):
Kelvin Zhange634bde2023-04-28 23:59:43 -0700426 return self.info_dict.get("virtual_ab_compression") == "true"
Kelvin Zhang563750f2021-04-28 12:46:17 -0400427
428 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700429 def is_android_r(self):
430 system_prop = self.info_dict.get("system.build.prop")
431 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
432
433 @property
Kelvin Zhang2f9a9ae2023-09-27 09:33:52 -0700434 def is_release_key(self):
435 system_prop = self.info_dict.get("build.prop")
436 return system_prop and system_prop.GetProp("ro.build.tags") == "release-key"
437
438 @property
Kelvin Zhang8f830002023-08-16 13:16:48 -0700439 def vabc_compression_param(self):
440 return self.get("virtual_ab_compression_method", "")
441
442 @property
David Anderson1c596172023-04-14 16:01:55 -0700443 def vendor_api_level(self):
444 vendor_prop = self.info_dict.get("vendor.build.prop")
445 if not vendor_prop:
446 return -1
447
448 props = [
449 "ro.board.api_level",
450 "ro.board.first_api_level",
451 "ro.product.first_api_level",
452 ]
453 for prop in props:
454 value = vendor_prop.GetProp(prop)
455 try:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700456 return int(value)
David Anderson1c596172023-04-14 16:01:55 -0700457 except:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700458 pass
David Anderson1c596172023-04-14 16:01:55 -0700459 return -1
460
461 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700462 def is_vabc_xor(self):
463 vendor_prop = self.info_dict.get("vendor.build.prop")
464 vabc_xor_enabled = vendor_prop and \
465 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
466 return vabc_xor_enabled
467
468 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400469 def vendor_suppressed_vabc(self):
470 vendor_prop = self.info_dict.get("vendor.build.prop")
471 vabc_suppressed = vendor_prop and \
472 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
473 return vabc_suppressed and vabc_suppressed.lower() == "true"
474
475 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def oem_props(self):
477 return self._oem_props
478
479 def __getitem__(self, key):
480 return self.info_dict[key]
481
482 def __setitem__(self, key, value):
483 self.info_dict[key] = value
484
485 def get(self, key, default=None):
486 return self.info_dict.get(key, default)
487
488 def items(self):
489 return self.info_dict.items()
490
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 def _GetRawBuildProp(self, prop, partition):
492 prop_file = '{}.build.prop'.format(
493 partition) if partition else 'build.prop'
494 partition_props = self.info_dict.get(prop_file)
495 if not partition_props:
496 return None
497 return partition_props.GetProp(prop)
498
Daniel Normand5fe8622020-01-08 17:01:11 -0800499 def GetPartitionBuildProp(self, prop, partition):
500 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800501
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000502 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000503 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000504 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800505
Daniel Normand5fe8622020-01-08 17:01:11 -0800506 # If provided a partition for this property, only look within that
507 # partition's build.prop.
508 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800509 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800510 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800511 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512
513 prop_val = self._GetRawBuildProp(prop, partition)
514 if prop_val is not None:
515 return prop_val
516 raise ExternalError("couldn't find %s in %s.build.prop" %
517 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800518
Tao Bao1c320f82019-10-04 23:25:12 -0700519 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700521 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
522 return self._ResolveRoProductBuildProp(prop)
523
Tianjiefdda51d2021-05-05 14:46:35 -0700524 if prop == "ro.build.id":
525 return self._GetBuildId()
526
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000527 prop_val = self._GetRawBuildProp(prop, None)
528 if prop_val is not None:
529 return prop_val
530
531 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700532
533 def _ResolveRoProductBuildProp(self, prop):
534 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000535 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700536 if prop_val:
537 return prop_val
538
Steven Laver8e2086e2020-04-27 16:26:31 -0700539 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000540 source_order_val = self._GetRawBuildProp(
541 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700542 if source_order_val:
543 source_order = source_order_val.split(",")
544 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700545 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700546
547 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700548 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700549 raise ExternalError(
550 "Invalid ro.product.property_source_order '{}'".format(source_order))
551
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000552 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700553 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000554 "ro.product", "ro.product.{}".format(source_partition), 1)
555 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700556 if prop_val:
557 return prop_val
558
559 raise ExternalError("couldn't resolve {}".format(prop))
560
Steven Laver8e2086e2020-04-27 16:26:31 -0700561 def _GetRoProductPropsDefaultSourceOrder(self):
562 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
563 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000564 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700565 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000566 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700567 if android_version == "10":
568 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
569 # NOTE: float() conversion of android_version will have rounding error.
570 # We are checking for "9" or less, and using "< 10" is well outside of
571 # possible floating point rounding.
572 try:
573 android_version_val = float(android_version)
574 except ValueError:
575 android_version_val = 0
576 if android_version_val < 10:
577 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
578 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
579
Tianjieb37c5be2020-10-15 21:27:10 -0700580 def _GetPlatformVersion(self):
581 version_sdk = self.GetBuildProp("ro.build.version.sdk")
582 # init code switches to version_release_or_codename (see b/158483506). After
583 # API finalization, release_or_codename will be the same as release. This
584 # is the best effort to support pre-S dev stage builds.
585 if int(version_sdk) >= 30:
586 try:
587 return self.GetBuildProp("ro.build.version.release_or_codename")
588 except ExternalError:
589 logger.warning('Failed to find ro.build.version.release_or_codename')
590
591 return self.GetBuildProp("ro.build.version.release")
592
Tianjiefdda51d2021-05-05 14:46:35 -0700593 def _GetBuildId(self):
594 build_id = self._GetRawBuildProp("ro.build.id", None)
595 if build_id:
596 return build_id
597
598 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
599 if not legacy_build_id:
600 raise ExternalError("Couldn't find build id in property file")
601
602 if self.use_legacy_id:
603 return legacy_build_id
604
605 # Append the top 8 chars of vbmeta digest to the existing build id. The
606 # logic needs to match the one in init, so that OTA can deliver correctly.
607 avb_enable = self.info_dict.get("avb_enable") == "true"
608 if not avb_enable:
609 raise ExternalError("AVB isn't enabled when using legacy build id")
610
611 vbmeta_digest = self.info_dict.get("vbmeta_digest")
612 if not vbmeta_digest:
613 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
614 " id")
615 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
616 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
617
618 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
619 return legacy_build_id + '.' + digest_prefix
620
Tianjieb37c5be2020-10-15 21:27:10 -0700621 def _GetPartitionPlatformVersion(self, partition):
622 try:
623 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
624 partition)
625 except ExternalError:
626 return self.GetPartitionBuildProp("ro.build.version.release",
627 partition)
628
Tao Bao1c320f82019-10-04 23:25:12 -0700629 def GetOemProperty(self, key):
630 if self.oem_props is not None and key in self.oem_props:
631 return self.oem_dicts[0][key]
632 return self.GetBuildProp(key)
633
Daniel Normand5fe8622020-01-08 17:01:11 -0800634 def GetPartitionFingerprint(self, partition):
635 return self._partition_fingerprints.get(partition, None)
636
637 def CalculatePartitionFingerprint(self, partition):
638 try:
639 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
640 except ExternalError:
641 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
642 self.GetPartitionBuildProp("ro.product.brand", partition),
643 self.GetPartitionBuildProp("ro.product.name", partition),
644 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700645 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800646 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400647 self.GetPartitionBuildProp(
648 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800649 self.GetPartitionBuildProp("ro.build.type", partition),
650 self.GetPartitionBuildProp("ro.build.tags", partition))
651
Tao Bao1c320f82019-10-04 23:25:12 -0700652 def CalculateFingerprint(self):
653 if self.oem_props is None:
654 try:
655 return self.GetBuildProp("ro.build.fingerprint")
656 except ExternalError:
657 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
658 self.GetBuildProp("ro.product.brand"),
659 self.GetBuildProp("ro.product.name"),
660 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700661 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700662 self.GetBuildProp("ro.build.id"),
663 self.GetBuildProp("ro.build.version.incremental"),
664 self.GetBuildProp("ro.build.type"),
665 self.GetBuildProp("ro.build.tags"))
666 return "%s/%s/%s:%s" % (
667 self.GetOemProperty("ro.product.brand"),
668 self.GetOemProperty("ro.product.name"),
669 self.GetOemProperty("ro.product.device"),
670 self.GetBuildProp("ro.build.thumbprint"))
671
672 def WriteMountOemScript(self, script):
673 assert self.oem_props is not None
674 recovery_mount_options = self.info_dict.get("recovery_mount_options")
675 script.Mount("/oem", recovery_mount_options)
676
677 def WriteDeviceAssertions(self, script, oem_no_mount):
678 # Read the property directly if not using OEM properties.
679 if not self.oem_props:
680 script.AssertDevice(self.device)
681 return
682
683 # Otherwise assert OEM properties.
684 if not self.oem_dicts:
685 raise ExternalError(
686 "No OEM file provided to answer expected assertions")
687
688 for prop in self.oem_props.split():
689 values = []
690 for oem_dict in self.oem_dicts:
691 if prop in oem_dict:
692 values.append(oem_dict[prop])
693 if not values:
694 raise ExternalError(
695 "The OEM file is missing the property %s" % (prop,))
696 script.AssertOemProperty(prop, values, oem_no_mount)
697
698
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700699def DoesInputFileContain(input_file, fn):
700 """Check whether the input target_files.zip contain an entry `fn`"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000701 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700702 return fn in input_file.namelist()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700703 elif zipfile.is_zipfile(input_file):
704 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700705 return fn in zfp.namelist()
706 else:
707 if not os.path.isdir(input_file):
708 raise ValueError(
709 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
710 path = os.path.join(input_file, *fn.split("/"))
711 return os.path.exists(path)
712
713
714def ReadBytesFromInputFile(input_file, fn):
715 """Reads the bytes of fn from input zipfile or directory."""
716 if isinstance(input_file, zipfile.ZipFile):
717 return input_file.read(fn)
718 elif zipfile.is_zipfile(input_file):
719 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
720 return zfp.read(fn)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000721 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700722 if not os.path.isdir(input_file):
723 raise ValueError(
724 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000725 path = os.path.join(input_file, *fn.split("/"))
726 try:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700727 with open(path, "rb") as f:
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000728 return f.read()
729 except IOError as e:
730 if e.errno == errno.ENOENT:
731 raise KeyError(fn)
732
733
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700734def ReadFromInputFile(input_file, fn):
735 """Reads the str contents of fn from input zipfile or directory."""
736 return ReadBytesFromInputFile(input_file, fn).decode()
737
738
Kelvin Zhang6b10e152023-05-02 15:48:16 -0700739def WriteBytesToInputFile(input_file, fn, data):
740 """Write bytes |data| contents to fn of input zipfile or directory."""
741 if isinstance(input_file, zipfile.ZipFile):
742 with input_file.open(fn, "w") as entry_fp:
743 return entry_fp.write(data)
744 elif zipfile.is_zipfile(input_file):
745 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
746 with zfp.open(fn, "w") as entry_fp:
747 return entry_fp.write(data)
748 else:
749 if not os.path.isdir(input_file):
750 raise ValueError(
751 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
752 path = os.path.join(input_file, *fn.split("/"))
753 try:
754 with open(path, "wb") as f:
755 return f.write(data)
756 except IOError as e:
757 if e.errno == errno.ENOENT:
758 raise KeyError(fn)
759
760
761def WriteToInputFile(input_file, fn, str: str):
762 """Write str content to fn of input file or directory"""
763 return WriteBytesToInputFile(input_file, fn, str.encode())
764
765
Yifan Hong10482a22021-01-07 14:38:41 -0800766def ExtractFromInputFile(input_file, fn):
767 """Extracts the contents of fn from input zipfile or directory into a file."""
768 if isinstance(input_file, zipfile.ZipFile):
769 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500770 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800771 f.write(input_file.read(fn))
772 return tmp_file
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700773 elif zipfile.is_zipfile(input_file):
774 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
775 tmp_file = MakeTempFile(os.path.basename(fn))
776 with open(tmp_file, "wb") as fp:
777 fp.write(zfp.read(fn))
778 return tmp_file
Yifan Hong10482a22021-01-07 14:38:41 -0800779 else:
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700780 if not os.path.isdir(input_file):
781 raise ValueError(
782 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800783 file = os.path.join(input_file, *fn.split("/"))
784 if not os.path.exists(file):
785 raise KeyError(fn)
786 return file
787
Kelvin Zhang563750f2021-04-28 12:46:17 -0400788
jiajia tangf3f842b2021-03-17 21:49:44 +0800789class RamdiskFormat(object):
790 LZ4 = 1
791 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800792
Kelvin Zhang563750f2021-04-28 12:46:17 -0400793
TJ Rhoades6f488e92022-05-01 22:16:22 -0700794def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800795 if info_dict.get('lz4_ramdisks') == 'true':
796 ramdisk_format = RamdiskFormat.LZ4
797 else:
798 ramdisk_format = RamdiskFormat.GZ
799 return ramdisk_format
800
Kelvin Zhang563750f2021-04-28 12:46:17 -0400801
Tao Bao410ad8b2018-08-24 12:08:38 -0700802def LoadInfoDict(input_file, repacking=False):
803 """Loads the key/value pairs from the given input target_files.
804
Tianjiea85bdf02020-07-29 11:56:19 -0700805 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700806 checks and returns the parsed key/value pairs for to the given build. It's
807 usually called early when working on input target_files files, e.g. when
808 generating OTAs, or signing builds. Note that the function may be called
809 against an old target_files file (i.e. from past dessert releases). So the
810 property parsing needs to be backward compatible.
811
812 In a `META/misc_info.txt`, a few properties are stored as links to the files
813 in the PRODUCT_OUT directory. It works fine with the build system. However,
814 they are no longer available when (re)generating images from target_files zip.
815 When `repacking` is True, redirect these properties to the actual files in the
816 unzipped directory.
817
818 Args:
819 input_file: The input target_files file, which could be an open
820 zipfile.ZipFile instance, or a str for the dir that contains the files
821 unzipped from a target_files file.
822 repacking: Whether it's trying repack an target_files file after loading the
823 info dict (default: False). If so, it will rewrite a few loaded
824 properties (e.g. selinux_fc, root_dir) to point to the actual files in
825 target_files file. When doing repacking, `input_file` must be a dir.
826
827 Returns:
828 A dict that contains the parsed key/value pairs.
829
830 Raises:
831 AssertionError: On invalid input arguments.
832 ValueError: On malformed input values.
833 """
834 if repacking:
835 assert isinstance(input_file, str), \
836 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700837
Doug Zongkerc9253822014-02-04 12:17:58 -0800838 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000839 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800840
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700841 try:
Michael Runge6e836112014-04-15 17:40:21 -0700842 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700843 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700844 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700845
Tao Bao410ad8b2018-08-24 12:08:38 -0700846 if "recovery_api_version" not in d:
847 raise ValueError("Failed to find 'recovery_api_version'")
848 if "fstab_version" not in d:
849 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800850
Tao Bao410ad8b2018-08-24 12:08:38 -0700851 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700852 # "selinux_fc" properties should point to the file_contexts files
853 # (file_contexts.bin) under META/.
854 for key in d:
855 if key.endswith("selinux_fc"):
856 fc_basename = os.path.basename(d[key])
857 fc_config = os.path.join(input_file, "META", fc_basename)
858 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700859
Daniel Norman72c626f2019-05-13 15:58:14 -0700860 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700861
Tom Cherryd14b8952018-08-09 14:26:00 -0700862 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700863 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700864 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700865 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700866
David Anderson0ec64ac2019-12-06 12:21:18 -0800867 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700868 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000869 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800870 key_name = part_name + "_base_fs_file"
871 if key_name not in d:
872 continue
873 basename = os.path.basename(d[key_name])
874 base_fs_file = os.path.join(input_file, "META", basename)
875 if os.path.exists(base_fs_file):
876 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700877 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700878 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800879 "Failed to find %s base fs file: %s", part_name, base_fs_file)
880 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700881
Doug Zongker37974732010-09-16 17:44:38 -0700882 def makeint(key):
883 if key in d:
884 d[key] = int(d[key], 0)
885
886 makeint("recovery_api_version")
887 makeint("blocksize")
888 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700889 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700890 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700891 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700892 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800893 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700894
Steve Muckle903a1ca2020-05-07 17:32:10 -0700895 boot_images = "boot.img"
896 if "boot_images" in d:
897 boot_images = d["boot_images"]
898 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400899 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700900
Tao Bao765668f2019-10-04 22:03:00 -0700901 # Load recovery fstab if applicable.
902 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700903 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800904
Tianjie Xu861f4132018-09-12 11:49:33 -0700905 # Tries to load the build props for all partitions with care_map, including
906 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800907 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800908 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000909 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800910 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700911 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800912
Tao Bao12d87fc2018-01-31 12:18:52 -0800913 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700914 # Set the vbmeta digest if exists
915 try:
916 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
917 except KeyError:
918 pass
919
Kelvin Zhang39aea442020-08-17 11:04:25 -0400920 try:
921 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
922 except KeyError:
923 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700924 return d
925
Tao Baod1de6f32017-03-01 16:38:48 -0800926
Daniel Norman4cc9df62019-07-18 10:11:07 -0700927def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900928 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700929 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900930
Daniel Norman4cc9df62019-07-18 10:11:07 -0700931
932def LoadDictionaryFromFile(file_path):
933 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900934 return LoadDictionaryFromLines(lines)
935
936
Michael Runge6e836112014-04-15 17:40:21 -0700937def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700938 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700939 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700940 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700941 if not line or line.startswith("#"):
942 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700943 if "=" in line:
944 name, value = line.split("=", 1)
945 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700946 return d
947
Tao Baod1de6f32017-03-01 16:38:48 -0800948
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000949class PartitionBuildProps(object):
950 """The class holds the build prop of a particular partition.
951
952 This class loads the build.prop and holds the build properties for a given
953 partition. It also partially recognizes the 'import' statement in the
954 build.prop; and calculates alternative values of some specific build
955 properties during runtime.
956
957 Attributes:
958 input_file: a zipped target-file or an unzipped target-file directory.
959 partition: name of the partition.
960 props_allow_override: a list of build properties to search for the
961 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000962 build_props: a dict of build properties for the given partition.
963 prop_overrides: a set of props that are overridden by import.
964 placeholder_values: A dict of runtime variables' values to replace the
965 placeholders in the build.prop file. We expect exactly one value for
966 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800967 ramdisk_format: If name is "boot", the format of ramdisk inside the
968 boot image. Otherwise, its value is ignored.
Elliott Hughes97ad1202023-06-20 16:41:58 -0700969 Use lz4 to decompress by default. If its value is gzip, use gzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000970 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400971
Tianjie Xu9afb2212020-05-10 21:48:15 +0000972 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000973 self.input_file = input_file
974 self.partition = name
975 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000976 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000977 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000978 self.prop_overrides = set()
979 self.placeholder_values = {}
980 if placeholder_values:
981 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000982
983 @staticmethod
984 def FromDictionary(name, build_props):
985 """Constructs an instance from a build prop dictionary."""
986
987 props = PartitionBuildProps("unknown", name)
988 props.build_props = build_props.copy()
989 return props
990
991 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800992 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000993 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800994
Devin Mooreafdd7c72021-12-13 22:04:08 +0000995 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400996 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000997 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800998 else:
999 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
1000
1001 props = PartitionBuildProps(input_file, name, placeholder_values)
1002 props._LoadBuildProp(data)
1003 return props
1004
1005 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +00001006 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -08001007 """
1008 Read build.prop for boot image from input_file.
1009 Return empty string if not found.
1010 """
Devin Mooreafdd7c72021-12-13 22:04:08 +00001011 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -08001012 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001013 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001014 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001015 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001016 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +08001017 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001018 if prop_file is None:
1019 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -05001020 with open(prop_file, "r") as f:
1021 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -08001022
1023 @staticmethod
1024 def _ReadPartitionPropFile(input_file, name):
1025 """
1026 Read build.prop for name from input_file.
1027 Return empty string if not found.
1028 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001029 data = ''
1030 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
1031 '{}/build.prop'.format(name.upper())]:
1032 try:
1033 data = ReadFromInputFile(input_file, prop_file)
1034 break
1035 except KeyError:
1036 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -08001037 if data == '':
1038 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -08001039 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001040
Yifan Hong125d0b62020-09-24 17:07:03 -07001041 @staticmethod
1042 def FromBuildPropFile(name, build_prop_file):
1043 """Constructs an instance from a build prop file."""
1044
1045 props = PartitionBuildProps("unknown", name)
1046 with open(build_prop_file) as f:
1047 props._LoadBuildProp(f.read())
1048 return props
1049
Tianjie Xu9afb2212020-05-10 21:48:15 +00001050 def _LoadBuildProp(self, data):
1051 for line in data.split('\n'):
1052 line = line.strip()
1053 if not line or line.startswith("#"):
1054 continue
1055 if line.startswith("import"):
1056 overrides = self._ImportParser(line)
1057 duplicates = self.prop_overrides.intersection(overrides.keys())
1058 if duplicates:
1059 raise ValueError('prop {} is overridden multiple times'.format(
1060 ','.join(duplicates)))
1061 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1062 self.build_props.update(overrides)
1063 elif "=" in line:
1064 name, value = line.split("=", 1)
1065 if name in self.prop_overrides:
1066 raise ValueError('prop {} is set again after overridden by import '
1067 'statement'.format(name))
1068 self.build_props[name] = value
1069
1070 def _ImportParser(self, line):
1071 """Parses the build prop in a given import statement."""
1072
1073 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001074 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001075 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001076
1077 if len(tokens) == 3:
1078 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1079 return {}
1080
Tianjie Xu9afb2212020-05-10 21:48:15 +00001081 import_path = tokens[1]
1082 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001083 logger.warn('Unrecognized import path {}'.format(line))
1084 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001085
1086 # We only recognize a subset of import statement that the init process
1087 # supports. And we can loose the restriction based on how the dynamic
1088 # fingerprint is used in practice. The placeholder format should be
1089 # ${placeholder}, and its value should be provided by the caller through
1090 # the placeholder_values.
1091 for prop, value in self.placeholder_values.items():
1092 prop_place_holder = '${{{}}}'.format(prop)
1093 if prop_place_holder in import_path:
1094 import_path = import_path.replace(prop_place_holder, value)
1095 if '$' in import_path:
1096 logger.info('Unresolved place holder in import path %s', import_path)
1097 return {}
1098
1099 import_path = import_path.replace('/{}'.format(self.partition),
1100 self.partition.upper())
1101 logger.info('Parsing build props override from %s', import_path)
1102
1103 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1104 d = LoadDictionaryFromLines(lines)
1105 return {key: val for key, val in d.items()
1106 if key in self.props_allow_override}
1107
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001108 def __getstate__(self):
1109 state = self.__dict__.copy()
1110 # Don't pickle baz
1111 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1112 state["input_file"] = state["input_file"].filename
1113 return state
1114
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001115 def GetProp(self, prop):
1116 return self.build_props.get(prop)
1117
1118
Tianjie Xucfa86222016-03-07 16:31:19 -08001119def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1120 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001121 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001122 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001123 self.mount_point = mount_point
1124 self.fs_type = fs_type
1125 self.device = device
1126 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001127 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001128 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001129
1130 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001131 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001132 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001133 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001134 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001135
Tao Baod1de6f32017-03-01 16:38:48 -08001136 assert fstab_version == 2
1137
1138 d = {}
1139 for line in data.split("\n"):
1140 line = line.strip()
1141 if not line or line.startswith("#"):
1142 continue
1143
1144 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1145 pieces = line.split()
1146 if len(pieces) != 5:
1147 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1148
1149 # Ignore entries that are managed by vold.
1150 options = pieces[4]
1151 if "voldmanaged=" in options:
1152 continue
1153
1154 # It's a good line, parse it.
1155 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001156 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001157 options = options.split(",")
1158 for i in options:
1159 if i.startswith("length="):
1160 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001161 elif i == "slotselect":
1162 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001163 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001164 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001165 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001166
Tao Baod1de6f32017-03-01 16:38:48 -08001167 mount_flags = pieces[3]
1168 # Honor the SELinux context if present.
1169 context = None
1170 for i in mount_flags.split(","):
1171 if i.startswith("context="):
1172 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001173
Tao Baod1de6f32017-03-01 16:38:48 -08001174 mount_point = pieces[1]
1175 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001176 device=pieces[0], length=length, context=context,
1177 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001178
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001179 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001180 # system. Other areas assume system is always at "/system" so point /system
1181 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001182 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001183 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001184 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001185 return d
1186
1187
Tao Bao765668f2019-10-04 22:03:00 -07001188def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1189 """Finds the path to recovery fstab and loads its contents."""
1190 # recovery fstab is only meaningful when installing an update via recovery
1191 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001192 if info_dict.get('ab_update') == 'true' and \
1193 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001194 return None
1195
1196 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1197 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1198 # cases, since it may load the info_dict from an old build (e.g. when
1199 # generating incremental OTAs from that build).
1200 system_root_image = info_dict.get('system_root_image') == 'true'
1201 if info_dict.get('no_recovery') != 'true':
1202 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
Kelvin Zhang2ab69862023-10-27 10:58:05 -07001203 if not DoesInputFileContain(input_file, recovery_fstab_path):
1204 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
Tao Bao765668f2019-10-04 22:03:00 -07001205 return LoadRecoveryFSTab(
1206 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1207 system_root_image)
1208
1209 if info_dict.get('recovery_as_boot') == 'true':
1210 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
Kelvin Zhang2ab69862023-10-27 10:58:05 -07001211 if not DoesInputFileContain(input_file, recovery_fstab_path):
1212 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
Tao Bao765668f2019-10-04 22:03:00 -07001213 return LoadRecoveryFSTab(
1214 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1215 system_root_image)
1216
1217 return None
1218
1219
Doug Zongker37974732010-09-16 17:44:38 -07001220def DumpInfoDict(d):
1221 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001222 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001223
Dan Albert8b72aef2015-03-23 19:13:21 -07001224
Daniel Norman55417142019-11-25 16:04:36 -08001225def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001226 """Merges dynamic partition info variables.
1227
1228 Args:
1229 framework_dict: The dictionary of dynamic partition info variables from the
1230 partial framework target files.
1231 vendor_dict: The dictionary of dynamic partition info variables from the
1232 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001233
1234 Returns:
1235 The merged dynamic partition info dictionary.
1236 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001237
1238 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001239 combined = set(a.split())
1240 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001241 combined = [item.strip() for item in combined if item.strip()]
1242 return " ".join(sorted(combined))
1243
1244 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001245 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001246 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1247
1248 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001249 # For keys-value pairs that are the same, copy to merged dict
1250 for key in vendor_dict.keys():
1251 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1252 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001253
1254 merged_dict["dynamic_partition_list"] = uniq_concat(
1255 framework_dict.get("dynamic_partition_list", ""),
1256 vendor_dict.get("dynamic_partition_list", ""))
1257
1258 # Super block devices are defined by the vendor dict.
1259 if "super_block_devices" in vendor_dict:
1260 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001261 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001262 key = "super_%s_device_size" % block_device
1263 if key not in vendor_dict:
1264 raise ValueError("Vendor dict does not contain required key %s." % key)
1265 merged_dict[key] = vendor_dict[key]
1266
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001267 # Partition groups and group sizes are defined by the vendor dict because
1268 # these values may vary for each board that uses a shared system image.
1269 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001270 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001271 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001272 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001273 if key not in vendor_dict:
1274 raise ValueError("Vendor dict does not contain required key %s." % key)
1275 merged_dict[key] = vendor_dict[key]
1276
1277 # Set the partition group's partition list using a concatenation of the
1278 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001279 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001280 merged_dict[key] = uniq_concat(
1281 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301282
Daniel Normanb0c75912020-09-24 14:30:21 -07001283 # Various other flags should be copied from the vendor dict, if defined.
1284 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1285 "super_metadata_device", "super_partition_error_limit",
1286 "super_partition_size"):
1287 if key in vendor_dict.keys():
1288 merged_dict[key] = vendor_dict[key]
1289
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001290 return merged_dict
1291
1292
Daniel Norman21c34f72020-11-11 17:25:50 -08001293def PartitionMapFromTargetFiles(target_files_dir):
1294 """Builds a map from partition -> path within an extracted target files directory."""
1295 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1296 possible_subdirs = {
1297 "system": ["SYSTEM"],
1298 "vendor": ["VENDOR", "SYSTEM/vendor"],
1299 "product": ["PRODUCT", "SYSTEM/product"],
1300 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1301 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1302 "vendor_dlkm": [
1303 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1304 ],
1305 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001306 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001307 }
1308 partition_map = {}
1309 for partition, subdirs in possible_subdirs.items():
1310 for subdir in subdirs:
1311 if os.path.exists(os.path.join(target_files_dir, subdir)):
1312 partition_map[partition] = subdir
1313 break
1314 return partition_map
1315
1316
Daniel Normand3351562020-10-29 12:33:11 -07001317def SharedUidPartitionViolations(uid_dict, partition_groups):
1318 """Checks for APK sharedUserIds that cross partition group boundaries.
1319
1320 This uses a single or merged build's shareduid_violation_modules.json
1321 output file, as generated by find_shareduid_violation.py or
1322 core/tasks/find-shareduid-violation.mk.
1323
1324 An error is defined as a sharedUserId that is found in a set of partitions
1325 that span more than one partition group.
1326
1327 Args:
1328 uid_dict: A dictionary created by using the standard json module to read a
1329 complete shareduid_violation_modules.json file.
1330 partition_groups: A list of groups, where each group is a list of
1331 partitions.
1332
1333 Returns:
1334 A list of error messages.
1335 """
1336 errors = []
1337 for uid, partitions in uid_dict.items():
1338 found_in_groups = [
1339 group for group in partition_groups
1340 if set(partitions.keys()) & set(group)
1341 ]
1342 if len(found_in_groups) > 1:
1343 errors.append(
1344 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1345 % (uid, ",".join(sorted(partitions.keys()))))
1346 return errors
1347
1348
Daniel Norman21c34f72020-11-11 17:25:50 -08001349def RunHostInitVerifier(product_out, partition_map):
1350 """Runs host_init_verifier on the init rc files within partitions.
1351
1352 host_init_verifier searches the etc/init path within each partition.
1353
1354 Args:
1355 product_out: PRODUCT_OUT directory, containing partition directories.
1356 partition_map: A map of partition name -> relative path within product_out.
1357 """
1358 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1359 cmd = ["host_init_verifier"]
1360 for partition, path in partition_map.items():
1361 if partition not in allowed_partitions:
1362 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1363 partition)
1364 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1365 # Add --property-contexts if the file exists on the partition.
1366 property_contexts = "%s_property_contexts" % (
1367 "plat" if partition == "system" else partition)
1368 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1369 property_contexts)
1370 if os.path.exists(property_contexts_path):
1371 cmd.append("--property-contexts=%s" % property_contexts_path)
1372 # Add the passwd file if the file exists on the partition.
1373 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1374 if os.path.exists(passwd_path):
1375 cmd.extend(["-p", passwd_path])
1376 return RunAndCheckOutput(cmd)
1377
1378
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001379def AppendAVBSigningArgs(cmd, partition, avb_salt=None):
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001380 """Append signing arguments for avbtool."""
1381 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
Kelvin Zhange634bde2023-04-28 23:59:43 -07001382 key_path = ResolveAVBSigningPathArgs(
1383 OPTIONS.info_dict.get("avb_" + partition + "_key_path"))
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001384 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1385 if key_path and algorithm:
1386 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001387 if avb_salt is None:
1388 avb_salt = OPTIONS.info_dict.get("avb_salt")
Tao Bao2b6dfd62017-09-27 17:17:43 -07001389 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001390 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001391 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001392
1393
zhangyongpeng70756972023-04-12 15:31:33 +08001394def ResolveAVBSigningPathArgs(split_args):
1395
1396 def ResolveBinaryPath(path):
1397 if os.path.exists(path):
1398 return path
Kelvin Zhang97a5afe2023-06-27 10:30:48 -07001399 if OPTIONS.search_path:
1400 new_path = os.path.join(OPTIONS.search_path, path)
1401 if os.path.exists(new_path):
1402 return new_path
zhangyongpeng70756972023-04-12 15:31:33 +08001403 raise ExternalError(
Kelvin Zhang43df0802023-07-24 13:16:03 -07001404 "Failed to find {}".format(path))
zhangyongpeng70756972023-04-12 15:31:33 +08001405
1406 if not split_args:
1407 return split_args
1408
1409 if isinstance(split_args, list):
1410 for index, arg in enumerate(split_args[:-1]):
1411 if arg == '--signing_helper':
1412 signing_helper_path = split_args[index + 1]
1413 split_args[index + 1] = ResolveBinaryPath(signing_helper_path)
1414 break
1415 elif isinstance(split_args, str):
1416 split_args = ResolveBinaryPath(split_args)
1417
1418 return split_args
1419
1420
Tao Bao765668f2019-10-04 22:03:00 -07001421def GetAvbPartitionArg(partition, image, info_dict=None):
Dennis Song4aae62e2023-10-02 04:31:34 +00001422 """Returns the VBMeta arguments for one partition.
Daniel Norman276f0622019-07-26 14:13:51 -07001423
1424 It sets up the VBMeta argument by including the partition descriptor from the
1425 given 'image', or by configuring the partition as a chained partition.
1426
1427 Args:
1428 partition: The name of the partition (e.g. "system").
1429 image: The path to the partition image.
1430 info_dict: A dict returned by common.LoadInfoDict(). Will use
1431 OPTIONS.info_dict if None has been given.
1432
1433 Returns:
Dennis Song4aae62e2023-10-02 04:31:34 +00001434 A list of VBMeta arguments for one partition.
Daniel Norman276f0622019-07-26 14:13:51 -07001435 """
1436 if info_dict is None:
1437 info_dict = OPTIONS.info_dict
1438
1439 # Check if chain partition is used.
1440 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001441 if not key_path:
Dennis Song6e5e44d2023-10-03 02:18:06 +00001442 return [AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG, image]
cfig1aeef722019-09-20 22:45:06 +08001443
1444 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1445 # into vbmeta.img. The recovery image will be configured on an independent
1446 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1447 # See details at
1448 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001449 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001450 return []
1451
1452 # Otherwise chain the partition into vbmeta.
1453 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
Dennis Song6e5e44d2023-10-03 02:18:06 +00001454 return [AVB_ARG_NAME_CHAIN_PARTITION, chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001455
1456
Dennis Song4aae62e2023-10-02 04:31:34 +00001457def GetAvbPartitionsArg(partitions,
1458 resolve_rollback_index_location_conflict=False,
1459 info_dict=None):
1460 """Returns the VBMeta arguments for all AVB partitions.
1461
1462 It sets up the VBMeta argument by calling GetAvbPartitionArg of all
1463 partitions.
1464
1465 Args:
1466 partitions: A dict of all AVB partitions.
1467 resolve_rollback_index_location_conflict: If true, resolve conflicting avb
1468 rollback index locations by assigning the smallest unused value.
1469 info_dict: A dict returned by common.LoadInfoDict().
1470
1471 Returns:
1472 A list of VBMeta arguments for all partitions.
1473 """
1474 # An AVB partition will be linked into a vbmeta partition by either
1475 # AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG or AVB_ARG_NAME_CHAIN_PARTITION, there
1476 # should be no other cases.
1477 valid_args = {
1478 AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG: [],
1479 AVB_ARG_NAME_CHAIN_PARTITION: []
1480 }
1481
1482 for partition, path in partitions.items():
1483 avb_partition_arg = GetAvbPartitionArg(partition, path, info_dict)
1484 if not avb_partition_arg:
1485 continue
1486 arg_name, arg_value = avb_partition_arg
1487 assert arg_name in valid_args
1488 valid_args[arg_name].append(arg_value)
1489
1490 # Copy the arguments for non-chained AVB partitions directly without
1491 # intervention.
1492 avb_args = []
1493 for image in valid_args[AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG]:
1494 avb_args.extend([AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG, image])
1495
1496 # Handle chained AVB partitions. The rollback index location might be
1497 # adjusted if two partitions use the same value. This may happen when mixing
1498 # a shared system image with other vendor images.
1499 used_index_loc = set()
1500 for chained_partition_arg in valid_args[AVB_ARG_NAME_CHAIN_PARTITION]:
1501 if resolve_rollback_index_location_conflict:
1502 while chained_partition_arg.rollback_index_location in used_index_loc:
1503 chained_partition_arg.rollback_index_location += 1
1504
1505 used_index_loc.add(chained_partition_arg.rollback_index_location)
1506 avb_args.extend([AVB_ARG_NAME_CHAIN_PARTITION,
1507 chained_partition_arg.to_string()])
1508
1509 return avb_args
1510
1511
Tao Bao02a08592018-07-22 12:40:45 -07001512def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1513 """Constructs and returns the arg to build or verify a chained partition.
1514
1515 Args:
1516 partition: The partition name.
1517 info_dict: The info dict to look up the key info and rollback index
1518 location.
1519 key: The key to be used for building or verifying the partition. Defaults to
1520 the key listed in info_dict.
1521
1522 Returns:
Dennis Song4aae62e2023-10-02 04:31:34 +00001523 An AvbChainedPartitionArg object with rollback_index_location and
1524 pubkey_path that can be used to build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001525 """
1526 if key is None:
1527 key = info_dict["avb_" + partition + "_key_path"]
zhangyongpeng70756972023-04-12 15:31:33 +08001528 key = ResolveAVBSigningPathArgs(key)
Tao Bao1ac886e2019-06-26 11:58:22 -07001529 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001530 rollback_index_location = info_dict[
1531 "avb_" + partition + "_rollback_index_location"]
Dennis Song4aae62e2023-10-02 04:31:34 +00001532 return AvbChainedPartitionArg(
1533 partition=partition,
1534 rollback_index_location=int(rollback_index_location),
1535 pubkey_path=pubkey_path)
Tao Bao02a08592018-07-22 12:40:45 -07001536
1537
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001538def _HasGkiCertificationArgs():
1539 return ("gki_signing_key_path" in OPTIONS.info_dict and
1540 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001541
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001542
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001543def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001544 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001545 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001546
zhangyongpeng70756972023-04-12 15:31:33 +08001547 key_path = ResolveAVBSigningPathArgs(key_path)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001548
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001549 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001550 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001551 raise ExternalError(
1552 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001553
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001554 output_certificate = tempfile.NamedTemporaryFile()
1555 cmd = [
1556 "generate_gki_certificate",
1557 "--name", image_name,
1558 "--algorithm", algorithm,
1559 "--key", key_path,
1560 "--output", output_certificate.name,
1561 image,
1562 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001563
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001564 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1565 signature_args = signature_args.strip()
1566 if signature_args:
1567 cmd.extend(["--additional_avb_args", signature_args])
1568
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001569 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001570 args = args.strip()
1571 if args:
1572 cmd.extend(["--additional_avb_args", args])
1573
1574 RunAndCheckOutput(cmd)
1575
1576 output_certificate.seek(os.SEEK_SET, 0)
1577 data = output_certificate.read()
1578 output_certificate.close()
1579 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001580
1581
Dennis Song4aae62e2023-10-02 04:31:34 +00001582def BuildVBMeta(image_path, partitions, name, needed_partitions,
1583 resolve_rollback_index_location_conflict=False):
Daniel Norman276f0622019-07-26 14:13:51 -07001584 """Creates a VBMeta image.
1585
1586 It generates the requested VBMeta image. The requested image could be for
1587 top-level or chained VBMeta image, which is determined based on the name.
1588
1589 Args:
1590 image_path: The output path for the new VBMeta image.
1591 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001592 values. Only valid partition names are accepted, as partitions listed
1593 in common.AVB_PARTITIONS and custom partitions listed in
1594 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001595 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1596 needed_partitions: Partitions whose descriptors should be included into the
1597 generated VBMeta image.
Dennis Song4aae62e2023-10-02 04:31:34 +00001598 resolve_rollback_index_location_conflict: If true, resolve conflicting avb
1599 rollback index locations by assigning the smallest unused value.
Daniel Norman276f0622019-07-26 14:13:51 -07001600
1601 Raises:
1602 AssertionError: On invalid input args.
1603 """
1604 avbtool = OPTIONS.info_dict["avb_avbtool"]
1605 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1606 AppendAVBSigningArgs(cmd, name)
1607
Hongguang Chenf23364d2020-04-27 18:36:36 -07001608 custom_partitions = OPTIONS.info_dict.get(
1609 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001610 custom_avb_partitions = ["vbmeta_" + part for part in OPTIONS.info_dict.get(
1611 "avb_custom_vbmeta_images_partition_list", "").strip().split()]
Hongguang Chenf23364d2020-04-27 18:36:36 -07001612
Dennis Song4aae62e2023-10-02 04:31:34 +00001613 avb_partitions = {}
Daniel Norman276f0622019-07-26 14:13:51 -07001614 for partition, path in partitions.items():
1615 if partition not in needed_partitions:
1616 continue
1617 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001618 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001619 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001620 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001621 'Unknown partition: {}'.format(partition)
1622 assert os.path.exists(path), \
1623 'Failed to find {} for {}'.format(path, partition)
Dennis Song4aae62e2023-10-02 04:31:34 +00001624 avb_partitions[partition] = path
1625 cmd.extend(GetAvbPartitionsArg(avb_partitions,
1626 resolve_rollback_index_location_conflict))
Daniel Norman276f0622019-07-26 14:13:51 -07001627
1628 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1629 if args and args.strip():
1630 split_args = shlex.split(args)
1631 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001632 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001633 # as a path relative to source tree, which may not be available at the
1634 # same location when running this script (we have the input target_files
1635 # zip only). For such cases, we additionally scan other locations (e.g.
1636 # IMAGES/, RADIO/, etc) before bailing out.
Dennis Song6e5e44d2023-10-03 02:18:06 +00001637 if arg == AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001638 chained_image = split_args[index + 1]
1639 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001640 continue
1641 found = False
1642 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1643 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001644 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001645 if os.path.exists(alt_path):
1646 split_args[index + 1] = alt_path
1647 found = True
1648 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001649 assert found, 'Failed to find {}'.format(chained_image)
zhangyongpeng70756972023-04-12 15:31:33 +08001650
1651 split_args = ResolveAVBSigningPathArgs(split_args)
Daniel Norman276f0622019-07-26 14:13:51 -07001652 cmd.extend(split_args)
1653
1654 RunAndCheckOutput(cmd)
1655
1656
jiajia tang836f76b2021-04-02 14:48:26 +08001657def _MakeRamdisk(sourcedir, fs_config_file=None,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001658 dev_node_file=None,
jiajia tang836f76b2021-04-02 14:48:26 +08001659 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001660 ramdisk_img = tempfile.NamedTemporaryFile()
1661
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001662 cmd = ["mkbootfs"]
1663
1664 if fs_config_file and os.access(fs_config_file, os.F_OK):
1665 cmd.extend(["-f", fs_config_file])
1666
1667 if dev_node_file and os.access(dev_node_file, os.F_OK):
1668 cmd.extend(["-n", dev_node_file])
1669
1670 cmd.append(os.path.join(sourcedir, "RAMDISK"))
1671
Steve Mucklee1b10862019-07-10 10:49:37 -07001672 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001673 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001674 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001675 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001676 elif ramdisk_format == RamdiskFormat.GZ:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001677 p2 = Run(["gzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001678 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001679 raise ValueError("Only support lz4 or gzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001680
1681 p2.wait()
1682 p1.wait()
1683 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001684 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001685
1686 return ramdisk_img
1687
1688
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001689def _BuildBootableImage(image_name, sourcedir, fs_config_file,
1690 dev_node_file=None, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001691 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001692 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001693
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001694 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001695 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1696 we are building a two-step special image (i.e. building a recovery image to
1697 be loaded into /boot in two-step OTAs).
1698
1699 Return the image data, or None if sourcedir does not appear to contains files
1700 for building the requested image.
1701 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001702
Yifan Hong63c5ca12020-10-08 11:54:02 -07001703 if info_dict is None:
1704 info_dict = OPTIONS.info_dict
1705
Steve Muckle9793cf62020-04-08 18:27:00 -07001706 # "boot" or "recovery", without extension.
1707 partition_name = os.path.basename(sourcedir).lower()
1708
Yifan Hong63c5ca12020-10-08 11:54:02 -07001709 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001710 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001711 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1712 logger.info("Excluded kernel binary from recovery image.")
1713 else:
1714 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001715 elif partition_name == "init_boot":
1716 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001717 else:
1718 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001719 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001720 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001721 return None
1722
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001723 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1724
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001725 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001726 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001727
Doug Zongkereef39442009-04-02 12:14:19 -07001728 img = tempfile.NamedTemporaryFile()
1729
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001730 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001731 ramdisk_format = GetRamdiskFormat(info_dict)
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001732 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, dev_node_file,
jiajia tang836f76b2021-04-02 14:48:26 +08001733 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001734
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001735 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1736 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1737
Yifan Hong63c5ca12020-10-08 11:54:02 -07001738 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001739 if kernel_path is not None:
1740 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001741
Benoit Fradina45a8682014-07-14 21:00:43 +02001742 fn = os.path.join(sourcedir, "second")
1743 if os.access(fn, os.F_OK):
1744 cmd.append("--second")
1745 cmd.append(fn)
1746
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001747 fn = os.path.join(sourcedir, "dtb")
1748 if os.access(fn, os.F_OK):
1749 cmd.append("--dtb")
1750 cmd.append(fn)
1751
Doug Zongker171f1cd2009-06-15 22:36:37 -07001752 fn = os.path.join(sourcedir, "cmdline")
1753 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001754 cmd.append("--cmdline")
1755 cmd.append(open(fn).read().rstrip("\n"))
1756
1757 fn = os.path.join(sourcedir, "base")
1758 if os.access(fn, os.F_OK):
1759 cmd.append("--base")
1760 cmd.append(open(fn).read().rstrip("\n"))
1761
Ying Wang4de6b5b2010-08-25 14:29:34 -07001762 fn = os.path.join(sourcedir, "pagesize")
1763 if os.access(fn, os.F_OK):
1764 cmd.append("--pagesize")
1765 cmd.append(open(fn).read().rstrip("\n"))
1766
Steve Mucklef84668e2020-03-16 19:13:46 -07001767 if partition_name == "recovery":
1768 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301769 if not args:
1770 # Fall back to "mkbootimg_args" for recovery image
1771 # in case "recovery_mkbootimg_args" is not set.
1772 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001773 elif partition_name == "init_boot":
1774 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001775 else:
1776 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001777 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001778 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001779
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001780 args = info_dict.get("mkbootimg_version_args")
1781 if args and args.strip():
1782 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001783
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001784 if has_ramdisk:
1785 cmd.extend(["--ramdisk", ramdisk_img.name])
1786
Tao Baod95e9fd2015-03-29 23:07:41 -07001787 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001788 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001789 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001790 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001791 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001792 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001793
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001794 if partition_name == "recovery":
1795 if info_dict.get("include_recovery_dtbo") == "true":
1796 fn = os.path.join(sourcedir, "recovery_dtbo")
1797 cmd.extend(["--recovery_dtbo", fn])
1798 if info_dict.get("include_recovery_acpio") == "true":
1799 fn = os.path.join(sourcedir, "recovery_acpio")
1800 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001801
Tao Bao986ee862018-10-04 15:46:16 -07001802 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001803
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001804 if _HasGkiCertificationArgs():
1805 if not os.path.exists(img.name):
1806 raise ValueError("Cannot find GKI boot.img")
1807 if kernel_path is None or not os.path.exists(kernel_path):
1808 raise ValueError("Cannot find GKI kernel.img")
1809
1810 # Certify GKI images.
1811 boot_signature_bytes = b''
1812 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1813 boot_signature_bytes += _GenerateGkiCertificate(
1814 kernel_path, "generic_kernel")
1815
1816 BOOT_SIGNATURE_SIZE = 16 * 1024
1817 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1818 raise ValueError(
1819 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1820 boot_signature_bytes += (
1821 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1822 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1823
1824 with open(img.name, 'ab') as f:
1825 f.write(boot_signature_bytes)
1826
Tao Baod95e9fd2015-03-29 23:07:41 -07001827 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001828 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001829 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001830 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001831 # We have switched from the prebuilt futility binary to using the tool
1832 # (futility-host) built from the source. Override the setting in the old
1833 # TF.zip.
1834 futility = info_dict["futility"]
1835 if futility.startswith("prebuilts/"):
1836 futility = "futility-host"
1837 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001838 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001839 info_dict["vboot_key"] + ".vbprivk",
1840 info_dict["vboot_subkey"] + ".vbprivk",
1841 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001842 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001843 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001844
Tao Baof3282b42015-04-01 11:21:55 -07001845 # Clean up the temp files.
1846 img_unsigned.close()
1847 img_keyblock.close()
1848
David Zeuthen8fecb282017-12-01 16:24:01 -05001849 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001850 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001851 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001852 if partition_name == "recovery":
1853 part_size = info_dict["recovery_size"]
1854 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001855 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001856 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001857 "--partition_size", str(part_size), "--partition_name",
1858 partition_name]
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001859 salt = None
1860 if kernel_path is not None:
1861 with open(kernel_path, "rb") as fp:
1862 salt = sha256(fp.read()).hexdigest()
1863 AppendAVBSigningArgs(cmd, partition_name, salt)
David Zeuthen8fecb282017-12-01 16:24:01 -05001864 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001865 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001866 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1867 cmd.extend(split_args)
Tao Bao986ee862018-10-04 15:46:16 -07001868 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001869
1870 img.seek(os.SEEK_SET, 0)
1871 data = img.read()
1872
1873 if has_ramdisk:
1874 ramdisk_img.close()
1875 img.close()
1876
1877 return data
1878
1879
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001880def _SignBootableImage(image_path, prebuilt_name, partition_name,
1881 info_dict=None):
1882 """Performs AVB signing for a prebuilt boot.img.
1883
1884 Args:
1885 image_path: The full path of the image, e.g., /path/to/boot.img.
1886 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001887 boot-5.10.img, recovery.img or init_boot.img.
1888 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001889 info_dict: The information dict read from misc_info.txt.
1890 """
1891 if info_dict is None:
1892 info_dict = OPTIONS.info_dict
1893
1894 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1895 if info_dict.get("avb_enable") == "true":
1896 avbtool = info_dict["avb_avbtool"]
1897 if partition_name == "recovery":
1898 part_size = info_dict["recovery_size"]
1899 else:
1900 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1901
1902 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1903 "--partition_size", str(part_size), "--partition_name",
1904 partition_name]
Kelvin Zhang160762a2023-10-17 12:27:56 -07001905 # Use sha256 of the kernel as salt for reproducible builds
1906 with tempfile.TemporaryDirectory() as tmpdir:
1907 RunAndCheckOutput(["unpack_bootimg", "--boot_img", image_path, "--out", tmpdir])
1908 for filename in ["kernel", "ramdisk", "vendor_ramdisk00"]:
1909 path = os.path.join(tmpdir, filename)
1910 if os.path.exists(path) and os.path.getsize(path):
1911 with open(path, "rb") as fp:
1912 salt = sha256(fp.read()).hexdigest()
1913 AppendAVBSigningArgs(cmd, partition_name, salt)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001914 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1915 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001916 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1917 cmd.extend(split_args)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001918 RunAndCheckOutput(cmd)
1919
1920
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001921def HasRamdisk(partition_name, info_dict=None):
1922 """Returns true/false to see if a bootable image should have a ramdisk.
1923
1924 Args:
1925 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1926 info_dict: The information dict read from misc_info.txt.
1927 """
1928 if info_dict is None:
1929 info_dict = OPTIONS.info_dict
1930
1931 if partition_name != "boot":
1932 return True # init_boot.img or recovery.img has a ramdisk.
1933
1934 if info_dict.get("recovery_as_boot") == "true":
1935 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1936
Bowgo Tsai85578e02022-04-19 10:50:59 +08001937 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1938 return False # A GKI boot.img has no ramdisk since Android-13.
1939
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001940 if info_dict.get("system_root_image") == "true":
1941 # The ramdisk content is merged into the system.img, so there is NO
1942 # ramdisk in the boot.img or boot-<kernel version>.img.
1943 return False
1944
1945 if info_dict.get("init_boot") == "true":
1946 # The ramdisk is moved to the init_boot.img, so there is NO
1947 # ramdisk in the boot.img or boot-<kernel version>.img.
1948 return False
1949
1950 return True
1951
1952
Doug Zongkerd5131602012-08-02 14:46:42 -07001953def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001954 info_dict=None, two_step_image=False,
1955 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001956 """Return a File object with the desired bootable image.
1957
1958 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1959 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1960 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001961
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001962 if info_dict is None:
1963 info_dict = OPTIONS.info_dict
1964
Doug Zongker55d93282011-01-25 17:03:34 -08001965 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1966 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001967 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001968 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001969
1970 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1971 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001972 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001973 return File.FromLocalFile(name, prebuilt_path)
1974
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001975 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001976 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1977 if os.path.exists(prebuilt_path):
1978 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1979 signed_img = MakeTempFile()
1980 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001981 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1982 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001983
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001984 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001985
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001986 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001987
Doug Zongker6f1d0312014-08-22 08:07:12 -07001988 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001989 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001990 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001991 os.path.join(unpack_dir, 'META/ramdisk_node_list')
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001992 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001993 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001994 if data:
1995 return File(name, data)
1996 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001997
Doug Zongkereef39442009-04-02 12:14:19 -07001998
Lucas Wei03230252022-04-18 16:00:40 +08001999def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07002000 """Build a vendor boot image from the specified sourcedir.
2001
2002 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
2003 turn them into a vendor boot image.
2004
2005 Return the image data, or None if sourcedir does not appear to contains files
2006 for building the requested image.
2007 """
2008
2009 if info_dict is None:
2010 info_dict = OPTIONS.info_dict
2011
2012 img = tempfile.NamedTemporaryFile()
2013
TJ Rhoades6f488e92022-05-01 22:16:22 -07002014 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08002015 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07002016
2017 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
2018 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
2019
2020 cmd = [mkbootimg]
2021
2022 fn = os.path.join(sourcedir, "dtb")
2023 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002024 has_vendor_kernel_boot = (info_dict.get(
2025 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08002026
2027 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
2028 # Otherwise pack dtb into vendor_boot.
2029 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
2030 cmd.append("--dtb")
2031 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07002032
2033 fn = os.path.join(sourcedir, "vendor_cmdline")
2034 if os.access(fn, os.F_OK):
2035 cmd.append("--vendor_cmdline")
2036 cmd.append(open(fn).read().rstrip("\n"))
2037
2038 fn = os.path.join(sourcedir, "base")
2039 if os.access(fn, os.F_OK):
2040 cmd.append("--base")
2041 cmd.append(open(fn).read().rstrip("\n"))
2042
2043 fn = os.path.join(sourcedir, "pagesize")
2044 if os.access(fn, os.F_OK):
2045 cmd.append("--pagesize")
2046 cmd.append(open(fn).read().rstrip("\n"))
2047
2048 args = info_dict.get("mkbootimg_args")
2049 if args and args.strip():
2050 cmd.extend(shlex.split(args))
2051
2052 args = info_dict.get("mkbootimg_version_args")
2053 if args and args.strip():
2054 cmd.extend(shlex.split(args))
2055
2056 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
2057 cmd.extend(["--vendor_boot", img.name])
2058
Devin Moore50509012021-01-13 10:45:04 -08002059 fn = os.path.join(sourcedir, "vendor_bootconfig")
2060 if os.access(fn, os.F_OK):
2061 cmd.append("--vendor_bootconfig")
2062 cmd.append(fn)
2063
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002064 ramdisk_fragment_imgs = []
2065 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
2066 if os.access(fn, os.F_OK):
2067 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
2068 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002069 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2070 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002071 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04002072 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2073 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002074 # Use prebuilt image if found, else create ramdisk from supplied files.
2075 if os.access(fn, os.F_OK):
2076 ramdisk_fragment_pathname = fn
2077 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002078 ramdisk_fragment_root = os.path.join(
2079 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08002080 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
2081 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002082 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
2083 ramdisk_fragment_pathname = ramdisk_fragment_img.name
2084 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
2085
Steve Mucklee1b10862019-07-10 10:49:37 -07002086 RunAndCheckOutput(cmd)
2087
2088 # AVB: if enabled, calculate and add hash.
2089 if info_dict.get("avb_enable") == "true":
2090 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08002091 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07002092 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08002093 "--partition_size", str(part_size), "--partition_name", partition_name]
2094 AppendAVBSigningArgs(cmd, partition_name)
2095 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07002096 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08002097 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
2098 cmd.extend(split_args)
Steve Mucklee1b10862019-07-10 10:49:37 -07002099 RunAndCheckOutput(cmd)
2100
2101 img.seek(os.SEEK_SET, 0)
2102 data = img.read()
2103
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002104 for f in ramdisk_fragment_imgs:
2105 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002106 ramdisk_img.close()
2107 img.close()
2108
2109 return data
2110
2111
2112def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2113 info_dict=None):
2114 """Return a File object with the desired vendor boot image.
2115
2116 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2117 the source files in 'unpack_dir'/'tree_subdir'."""
2118
2119 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2120 if os.path.exists(prebuilt_path):
2121 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2122 return File.FromLocalFile(name, prebuilt_path)
2123
2124 logger.info("building image from target_files %s...", tree_subdir)
2125
2126 if info_dict is None:
2127 info_dict = OPTIONS.info_dict
2128
Kelvin Zhang0876c412020-06-23 15:06:58 -04002129 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002130 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2131 if data:
2132 return File(name, data)
2133 return None
2134
2135
2136def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002137 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002138 """Return a File object with the desired vendor kernel boot image.
2139
2140 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2141 the source files in 'unpack_dir'/'tree_subdir'."""
2142
2143 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2144 if os.path.exists(prebuilt_path):
2145 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2146 return File.FromLocalFile(name, prebuilt_path)
2147
2148 logger.info("building image from target_files %s...", tree_subdir)
2149
2150 if info_dict is None:
2151 info_dict = OPTIONS.info_dict
2152
2153 data = _BuildVendorBootImage(
2154 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002155 if data:
2156 return File(name, data)
2157 return None
2158
2159
Narayan Kamatha07bf042017-08-14 14:49:21 +01002160def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002161 """Gunzips the given gzip compressed file to a given output file."""
2162 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002163 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002164 shutil.copyfileobj(in_file, out_file)
2165
2166
Kelvin Zhange473ce92023-06-21 13:06:59 -07002167def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
2168 # 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
2169 # higher bits of |external_attr| are unix file permission and types
2170 unix_filetype = info.external_attr >> 16
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002171 file_perm = unix_filetype & 0o777
Kelvin Zhange473ce92023-06-21 13:06:59 -07002172
2173 def CheckMask(a, mask):
2174 return (a & mask) == mask
2175
2176 def IsSymlink(a):
2177 return CheckMask(a, stat.S_IFLNK)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002178
2179 def IsDir(a):
2180 return CheckMask(a, stat.S_IFDIR)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002181 # python3.11 zipfile implementation doesn't handle symlink correctly
2182 if not IsSymlink(unix_filetype):
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002183 target = input_zip.extract(info, dirname)
2184 # We want to ensure that the file is at least read/writable by owner and readable by all users
2185 if IsDir(unix_filetype):
2186 os.chmod(target, file_perm | 0o755)
2187 else:
2188 os.chmod(target, file_perm | 0o644)
2189 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002190 if dirname is None:
2191 dirname = os.getcwd()
2192 target = os.path.join(dirname, info.filename)
2193 os.makedirs(os.path.dirname(target), exist_ok=True)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002194 if os.path.exists(target):
2195 os.unlink(target)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002196 os.symlink(input_zip.read(info).decode(), target)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002197 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002198
2199
Tao Bao0ff15de2019-03-20 11:26:06 -07002200def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002201 """Unzips the archive to the given directory.
2202
2203 Args:
2204 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002205 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002206 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2207 archvie. Non-matching patterns will be filtered out. If there's no match
2208 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002209 """
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002210 with zipfile.ZipFile(filename, allowZip64=True, mode="r") as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002211 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002212 entries = input_zip.infolist()
2213 # b/283033491
2214 # Per https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
2215 # In zip64 mode, central directory record's header_offset field might be
2216 # set to 0xFFFFFFFF if header offset is > 2^32. In this case, the extra
2217 # fields will contain an 8 byte little endian integer at offset 20
2218 # to indicate the actual local header offset.
2219 # As of python3.11, python does not handle zip64 central directories
2220 # correctly, so we will manually do the parsing here.
Kelvin Zhang1e774242023-06-17 09:18:15 -07002221
2222 # ZIP64 central directory extra field has two required fields:
2223 # 2 bytes header ID and 2 bytes size field. Thes two require fields have
2224 # a total size of 4 bytes. Then it has three other 8 bytes field, followed
2225 # by a 4 byte disk number field. The last disk number field is not required
2226 # to be present, but if it is present, the total size of extra field will be
2227 # divisible by 8(because 2+2+4+8*n is always going to be multiple of 8)
2228 # Most extra fields are optional, but when they appear, their must appear
2229 # in the order defined by zip64 spec. Since file header offset is the 2nd
2230 # to last field in zip64 spec, it will only be at last 8 bytes or last 12-4
2231 # bytes, depending on whether disk number is present.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002232 for entry in entries:
Kelvin Zhang1e774242023-06-17 09:18:15 -07002233 if entry.header_offset == 0xFFFFFFFF:
2234 if len(entry.extra) % 8 == 0:
2235 entry.header_offset = int.from_bytes(entry.extra[-12:-4], "little")
2236 else:
2237 entry.header_offset = int.from_bytes(entry.extra[-8:], "little")
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002238 if patterns is not None:
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002239 filtered = [info for info in entries if any(
2240 [fnmatch.fnmatch(info.filename, p) for p in patterns])]
Tao Bao0ff15de2019-03-20 11:26:06 -07002241
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002242 # There isn't any matching files. Don't unzip anything.
2243 if not filtered:
2244 return
Kelvin Zhange473ce92023-06-21 13:06:59 -07002245 for info in filtered:
2246 UnzipSingleFile(input_zip, info, dirname)
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002247 else:
Kelvin Zhange473ce92023-06-21 13:06:59 -07002248 for info in entries:
2249 UnzipSingleFile(input_zip, info, dirname)
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002250
2251
Daniel Norman78554ea2021-09-14 10:29:38 -07002252def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002253 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002254
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002255 Args:
2256 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2257 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2258
Daniel Norman78554ea2021-09-14 10:29:38 -07002259 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002260 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002261
Tao Bao1c830bf2017-12-25 10:43:47 -08002262 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002263 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002264 """
Doug Zongkereef39442009-04-02 12:14:19 -07002265
Tao Bao1c830bf2017-12-25 10:43:47 -08002266 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002267 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2268 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002269 UnzipToDir(m.group(1), tmp, patterns)
2270 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002271 filename = m.group(1)
2272 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002273 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002274
Tao Baodba59ee2018-01-09 13:21:02 -08002275 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002276
2277
Yifan Hong8a66a712019-04-04 15:37:57 -07002278def GetUserImage(which, tmpdir, input_zip,
2279 info_dict=None,
2280 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002281 reset_file_map=False):
2282 """Returns an Image object suitable for passing to BlockImageDiff.
2283
2284 This function loads the specified image from the given path. If the specified
2285 image is sparse, it also performs additional processing for OTA purpose. For
2286 example, it always adds block 0 to clobbered blocks list. It also detects
2287 files that cannot be reconstructed from the block list, for whom we should
2288 avoid applying imgdiff.
2289
2290 Args:
2291 which: The partition name.
2292 tmpdir: The directory that contains the prebuilt image and block map file.
2293 input_zip: The target-files ZIP archive.
2294 info_dict: The dict to be looked up for relevant info.
2295 allow_shared_blocks: If image is sparse, whether having shared blocks is
2296 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002297 reset_file_map: If true and image is sparse, reset file map before returning
2298 the image.
2299 Returns:
2300 A Image object. If it is a sparse image and reset_file_map is False, the
2301 image will have file_map info loaded.
2302 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002303 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002304 info_dict = LoadInfoDict(input_zip)
2305
Kelvin Zhang04521282023-03-02 09:42:52 -08002306 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002307
2308 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2309 # shared blocks (i.e. some blocks will show up in multiple files' block
2310 # list). We can only allocate such shared blocks to the first "owner", and
2311 # disable imgdiff for all later occurrences.
2312 if allow_shared_blocks is None:
2313 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2314
2315 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002316 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002317 if reset_file_map:
2318 img.ResetFileMap()
2319 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002320 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002321
2322
hungweichencc9c05d2022-08-23 05:45:42 +00002323def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002324 """Returns a Image object suitable for passing to BlockImageDiff.
2325
2326 This function loads the specified non-sparse image from the given path.
2327
2328 Args:
2329 which: The partition name.
2330 tmpdir: The directory that contains the prebuilt image and block map file.
2331 Returns:
2332 A Image object.
2333 """
2334 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2335 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2336
2337 # The image and map files must have been created prior to calling
2338 # ota_from_target_files.py (since LMP).
2339 assert os.path.exists(path) and os.path.exists(mappath)
2340
hungweichencc9c05d2022-08-23 05:45:42 +00002341 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002342
Yifan Hong8a66a712019-04-04 15:37:57 -07002343
hungweichencc9c05d2022-08-23 05:45:42 +00002344def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002345 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2346
2347 This function loads the specified sparse image from the given path, and
2348 performs additional processing for OTA purpose. For example, it always adds
2349 block 0 to clobbered blocks list. It also detects files that cannot be
2350 reconstructed from the block list, for whom we should avoid applying imgdiff.
2351
2352 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002353 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002354 tmpdir: The directory that contains the prebuilt image and block map file.
2355 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002356 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002357 Returns:
2358 A SparseImage object, with file_map info loaded.
2359 """
Tao Baoc765cca2018-01-31 17:32:40 -08002360 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2361 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2362
2363 # The image and map files must have been created prior to calling
2364 # ota_from_target_files.py (since LMP).
2365 assert os.path.exists(path) and os.path.exists(mappath)
2366
2367 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2368 # it to clobbered_blocks so that it will be written to the target
2369 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2370 clobbered_blocks = "0"
2371
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002372 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002373 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002374
2375 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2376 # if they contain all zeros. We can't reconstruct such a file from its block
2377 # list. Tag such entries accordingly. (Bug: 65213616)
2378 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002379 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002380 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002381 continue
2382
Tom Cherryd14b8952018-08-09 14:26:00 -07002383 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2384 # filename listed in system.map may contain an additional leading slash
2385 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2386 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002387 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002388 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002389 arcname = entry.lstrip('/')
2390 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002391 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002392 else:
2393 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002394
2395 assert arcname in input_zip.namelist(), \
2396 "Failed to find the ZIP entry for {}".format(entry)
2397
Tao Baoc765cca2018-01-31 17:32:40 -08002398 info = input_zip.getinfo(arcname)
2399 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002400
2401 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002402 # image, check the original block list to determine its completeness. Note
2403 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002404 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002405 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002406
Tao Baoc765cca2018-01-31 17:32:40 -08002407 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2408 ranges.extra['incomplete'] = True
2409
2410 return image
2411
2412
Doug Zongkereef39442009-04-02 12:14:19 -07002413def GetKeyPasswords(keylist):
2414 """Given a list of keys, prompt the user to enter passwords for
2415 those which require them. Return a {key: password} dict. password
2416 will be None if the key has no password."""
2417
Doug Zongker8ce7c252009-05-22 13:34:54 -07002418 no_passwords = []
2419 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002420 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002421 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002422
2423 # sorted() can't compare strings to None, so convert Nones to strings
2424 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002425 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002426 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002427 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002428 continue
2429
T.R. Fullhart37e10522013-03-18 10:31:26 -07002430 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002431 "-inform", "DER", "-nocrypt"],
2432 stdin=devnull.fileno(),
2433 stdout=devnull.fileno(),
2434 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002435 p.communicate()
2436 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002437 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002438 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002439 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002440 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2441 "-inform", "DER", "-passin", "pass:"],
2442 stdin=devnull.fileno(),
2443 stdout=devnull.fileno(),
2444 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002445 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002446 if p.returncode == 0:
2447 # Encrypted key with empty string as password.
2448 key_passwords[k] = ''
2449 elif stderr.startswith('Error decrypting key'):
2450 # Definitely encrypted key.
2451 # It would have said "Error reading key" if it didn't parse correctly.
2452 need_passwords.append(k)
2453 else:
2454 # Potentially, a type of key that openssl doesn't understand.
2455 # We'll let the routines in signapk.jar handle it.
2456 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002457 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002458
T.R. Fullhart37e10522013-03-18 10:31:26 -07002459 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002460 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002461 return key_passwords
2462
2463
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002464def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002465 """Gets the minSdkVersion declared in the APK.
2466
Martin Stjernholm58472e82022-01-07 22:08:47 +00002467 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2468 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002469
2470 Args:
2471 apk_name: The APK filename.
2472
2473 Returns:
2474 The parsed SDK version string.
2475
2476 Raises:
2477 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002478 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002479 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002480 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002481 stderr=subprocess.PIPE)
2482 stdoutdata, stderrdata = proc.communicate()
2483 if proc.returncode != 0:
2484 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002485 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2486 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002487
Tao Baof47bf0f2018-03-21 23:28:51 -07002488 for line in stdoutdata.split("\n"):
James Wuc5e321a2023-08-01 17:45:35 +00002489 # Due to ag/24161708, looking for lines such as minSdkVersion:'23',minSdkVersion:'M'
2490 # or sdkVersion:'23', sdkVersion:'M'.
2491 m = re.match(r'(?:minSdkVersion|sdkVersion):\'([^\']*)\'', line)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002492 if m:
2493 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002494 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002495
2496
2497def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002498 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002499
Tao Baof47bf0f2018-03-21 23:28:51 -07002500 If minSdkVersion is set to a codename, it is translated to a number using the
2501 provided map.
2502
2503 Args:
2504 apk_name: The APK filename.
2505
2506 Returns:
2507 The parsed SDK version number.
2508
2509 Raises:
2510 ExternalError: On failing to get the min SDK version number.
2511 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002512 version = GetMinSdkVersion(apk_name)
2513 try:
2514 return int(version)
2515 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002516 # Not a decimal number.
2517 #
2518 # It could be either a straight codename, e.g.
2519 # UpsideDownCake
2520 #
2521 # Or a codename with API fingerprint SHA, e.g.
2522 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2523 #
2524 # Extract the codename and try and map it to a version number.
2525 split = version.split(".")
2526 codename = split[0]
2527 if codename in codename_to_api_level_map:
2528 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002529 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002530 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2531 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002532
2533
2534def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002535 codename_to_api_level_map=None, whole_file=False,
2536 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002537 """Sign the input_name zip/jar/apk, producing output_name. Use the
2538 given key and password (the latter may be None if the key does not
2539 have a password.
2540
Doug Zongker951495f2009-08-14 12:44:19 -07002541 If whole_file is true, use the "-w" option to SignApk to embed a
2542 signature that covers the whole file in the archive comment of the
2543 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002544
2545 min_api_level is the API Level (int) of the oldest platform this file may end
2546 up on. If not specified for an APK, the API Level is obtained by interpreting
2547 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2548
2549 codename_to_api_level_map is needed to translate the codename which may be
2550 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002551
2552 Caller may optionally specify extra args to be passed to SignApk, which
2553 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002554 """
Tao Bao76def242017-11-21 09:25:31 -08002555 if codename_to_api_level_map is None:
2556 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002557 if extra_signapk_args is None:
2558 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002559
Alex Klyubin9667b182015-12-10 13:38:50 -08002560 java_library_path = os.path.join(
2561 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2562
Tao Baoe95540e2016-11-08 12:08:53 -08002563 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2564 ["-Djava.library.path=" + java_library_path,
2565 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002566 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002567 if whole_file:
2568 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002569
2570 min_sdk_version = min_api_level
2571 if min_sdk_version is None:
2572 if not whole_file:
2573 min_sdk_version = GetMinSdkVersionInt(
2574 input_name, codename_to_api_level_map)
2575 if min_sdk_version is not None:
2576 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2577
T.R. Fullhart37e10522013-03-18 10:31:26 -07002578 cmd.extend([key + OPTIONS.public_key_suffix,
2579 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002580 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002581
Tao Bao73dd4f42018-10-04 16:25:33 -07002582 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002583 if password is not None:
2584 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002585 stdoutdata, _ = proc.communicate(password)
2586 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002587 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002588 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002589 proc.returncode, stdoutdata))
2590
Doug Zongkereef39442009-04-02 12:14:19 -07002591
Doug Zongker37974732010-09-16 17:44:38 -07002592def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002593 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002594
Tao Bao9dd909e2017-11-14 11:27:32 -08002595 For non-AVB images, raise exception if the data is too big. Print a warning
2596 if the data is nearing the maximum size.
2597
2598 For AVB images, the actual image size should be identical to the limit.
2599
2600 Args:
2601 data: A string that contains all the data for the partition.
2602 target: The partition name. The ".img" suffix is optional.
2603 info_dict: The dict to be looked up for relevant info.
2604 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002605 if target.endswith(".img"):
2606 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002607 mount_point = "/" + target
2608
Ying Wangf8824af2014-06-03 14:07:27 -07002609 fs_type = None
2610 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002611 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002612 if mount_point == "/userdata":
2613 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002614 p = info_dict["fstab"][mount_point]
2615 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002616 device = p.device
2617 if "/" in device:
2618 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002619 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002620 if not fs_type or not limit:
2621 return
Doug Zongkereef39442009-04-02 12:14:19 -07002622
Andrew Boie0f9aec82012-02-14 09:32:52 -08002623 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002624 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2625 # path.
2626 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2627 if size != limit:
2628 raise ExternalError(
2629 "Mismatching image size for %s: expected %d actual %d" % (
2630 target, limit, size))
2631 else:
2632 pct = float(size) * 100.0 / limit
2633 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2634 if pct >= 99.0:
2635 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002636
2637 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002638 logger.warning("\n WARNING: %s\n", msg)
2639 else:
2640 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002641
2642
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002643def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002644 """Parses the APK certs info from a given target-files zip.
2645
2646 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2647 tuple with the following elements: (1) a dictionary that maps packages to
2648 certs (based on the "certificate" and "private_key" attributes in the file;
2649 (2) a string representing the extension of compressed APKs in the target files
2650 (e.g ".gz", ".bro").
2651
2652 Args:
2653 tf_zip: The input target_files ZipFile (already open).
2654
2655 Returns:
2656 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2657 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2658 no compressed APKs.
2659 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002660 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002661 compressed_extension = None
2662
Tao Bao0f990332017-09-08 19:02:54 -07002663 # META/apkcerts.txt contains the info for _all_ the packages known at build
2664 # time. Filter out the ones that are not installed.
2665 installed_files = set()
2666 for name in tf_zip.namelist():
2667 basename = os.path.basename(name)
2668 if basename:
2669 installed_files.add(basename)
2670
Tao Baoda30cfa2017-12-01 16:19:46 -08002671 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002672 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002673 if not line:
2674 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002675 m = re.match(
2676 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002677 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2678 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002679 line)
2680 if not m:
2681 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002682
Tao Bao818ddf52018-01-05 11:17:34 -08002683 matches = m.groupdict()
2684 cert = matches["CERT"]
2685 privkey = matches["PRIVKEY"]
2686 name = matches["NAME"]
2687 this_compressed_extension = matches["COMPRESSED"]
2688
2689 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2690 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2691 if cert in SPECIAL_CERT_STRINGS and not privkey:
2692 certmap[name] = cert
2693 elif (cert.endswith(OPTIONS.public_key_suffix) and
2694 privkey.endswith(OPTIONS.private_key_suffix) and
2695 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2696 certmap[name] = cert[:-public_key_suffix_len]
2697 else:
2698 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2699
2700 if not this_compressed_extension:
2701 continue
2702
2703 # Only count the installed files.
2704 filename = name + '.' + this_compressed_extension
2705 if filename not in installed_files:
2706 continue
2707
2708 # Make sure that all the values in the compression map have the same
2709 # extension. We don't support multiple compression methods in the same
2710 # system image.
2711 if compressed_extension:
2712 if this_compressed_extension != compressed_extension:
2713 raise ValueError(
2714 "Multiple compressed extensions: {} vs {}".format(
2715 compressed_extension, this_compressed_extension))
2716 else:
2717 compressed_extension = this_compressed_extension
2718
2719 return (certmap,
2720 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002721
2722
Doug Zongkereef39442009-04-02 12:14:19 -07002723COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002724Global options
2725
2726 -p (--path) <dir>
2727 Prepend <dir>/bin to the list of places to search for binaries run by this
2728 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002729
Doug Zongker05d3dea2009-06-22 11:32:31 -07002730 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002731 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002732
Tao Bao30df8b42018-04-23 15:32:53 -07002733 -x (--extra) <key=value>
2734 Add a key/value pair to the 'extras' dict, which device-specific extension
2735 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002736
Doug Zongkereef39442009-04-02 12:14:19 -07002737 -v (--verbose)
2738 Show command lines being executed.
2739
2740 -h (--help)
2741 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002742
2743 --logfile <file>
2744 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002745"""
2746
Kelvin Zhang0876c412020-06-23 15:06:58 -04002747
Doug Zongkereef39442009-04-02 12:14:19 -07002748def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002749 print(docstring.rstrip("\n"))
2750 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002751
2752
2753def ParseOptions(argv,
2754 docstring,
2755 extra_opts="", extra_long_opts=(),
2756 extra_option_handler=None):
2757 """Parse the options in argv and return any arguments that aren't
2758 flags. docstring is the calling module's docstring, to be displayed
2759 for errors and -h. extra_opts and extra_long_opts are for flags
2760 defined by the caller, which are processed by passing them to
2761 extra_option_handler."""
2762
2763 try:
2764 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002765 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002766 ["help", "verbose", "path=", "signapk_path=",
Thiébaud Weksteen62865ca2023-10-18 11:08:47 +11002767 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002768 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002769 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2770 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002771 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002772 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002773 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002774 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002775 sys.exit(2)
2776
Doug Zongkereef39442009-04-02 12:14:19 -07002777 for o, a in opts:
2778 if o in ("-h", "--help"):
2779 Usage(docstring)
2780 sys.exit()
2781 elif o in ("-v", "--verbose"):
2782 OPTIONS.verbose = True
2783 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002784 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002785 elif o in ("--signapk_path",):
2786 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002787 elif o in ("--signapk_shared_library_path",):
2788 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002789 elif o in ("--extra_signapk_args",):
2790 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002791 elif o in ("--aapt2_path",):
2792 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002793 elif o in ("--java_path",):
2794 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002795 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002796 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002797 elif o in ("--android_jar_path",):
2798 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002799 elif o in ("--public_key_suffix",):
2800 OPTIONS.public_key_suffix = a
2801 elif o in ("--private_key_suffix",):
2802 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002803 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002804 raise ValueError(
2805 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002806 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002807 raise ValueError(
2808 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002809 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002810 raise ValueError(
2811 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002812 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002813 raise ValueError(
2814 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002815 elif o in ("-s", "--device_specific"):
2816 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002817 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002818 key, value = a.split("=", 1)
2819 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002820 elif o in ("--logfile",):
2821 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002822 else:
2823 if extra_option_handler is None or not extra_option_handler(o, a):
2824 assert False, "unknown option \"%s\"" % (o,)
2825
Doug Zongker85448772014-09-09 14:59:20 -07002826 if OPTIONS.search_path:
2827 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2828 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002829
2830 return args
2831
2832
Tao Bao4c851b12016-09-19 13:54:38 -07002833def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002834 """Make a temp file and add it to the list of things to be deleted
2835 when Cleanup() is called. Return the filename."""
2836 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2837 os.close(fd)
2838 OPTIONS.tempfiles.append(fn)
2839 return fn
2840
2841
Tao Bao1c830bf2017-12-25 10:43:47 -08002842def MakeTempDir(prefix='tmp', suffix=''):
2843 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2844
2845 Returns:
2846 The absolute pathname of the new directory.
2847 """
2848 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2849 OPTIONS.tempfiles.append(dir_name)
2850 return dir_name
2851
2852
Doug Zongkereef39442009-04-02 12:14:19 -07002853def Cleanup():
2854 for i in OPTIONS.tempfiles:
Kelvin Zhang22680912023-05-19 13:12:59 -07002855 if not os.path.exists(i):
2856 continue
Doug Zongkereef39442009-04-02 12:14:19 -07002857 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002858 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002859 else:
2860 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002861 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002862
2863
2864class PasswordManager(object):
2865 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002866 self.editor = os.getenv("EDITOR")
2867 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002868
2869 def GetPasswords(self, items):
2870 """Get passwords corresponding to each string in 'items',
2871 returning a dict. (The dict may have keys in addition to the
2872 values in 'items'.)
2873
2874 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2875 user edit that file to add more needed passwords. If no editor is
2876 available, or $ANDROID_PW_FILE isn't define, prompts the user
2877 interactively in the ordinary way.
2878 """
2879
2880 current = self.ReadFile()
2881
2882 first = True
2883 while True:
2884 missing = []
2885 for i in items:
2886 if i not in current or not current[i]:
2887 missing.append(i)
2888 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002889 if not missing:
2890 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002891
2892 for i in missing:
2893 current[i] = ""
2894
2895 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002896 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002897 if sys.version_info[0] >= 3:
2898 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002899 answer = raw_input("try to edit again? [y]> ").strip()
2900 if answer and answer[0] not in 'yY':
2901 raise RuntimeError("key passwords unavailable")
2902 first = False
2903
2904 current = self.UpdateAndReadFile(current)
2905
Kelvin Zhang0876c412020-06-23 15:06:58 -04002906 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002907 """Prompt the user to enter a value (password) for each key in
2908 'current' whose value is fales. Returns a new dict with all the
2909 values.
2910 """
2911 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002912 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002913 if v:
2914 result[k] = v
2915 else:
2916 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002917 result[k] = getpass.getpass(
2918 "Enter password for %s key> " % k).strip()
2919 if result[k]:
2920 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002921 return result
2922
2923 def UpdateAndReadFile(self, current):
2924 if not self.editor or not self.pwfile:
2925 return self.PromptResult(current)
2926
2927 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002928 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002929 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2930 f.write("# (Additional spaces are harmless.)\n\n")
2931
2932 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002933 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002934 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002935 f.write("[[[ %s ]]] %s\n" % (v, k))
2936 if not v and first_line is None:
2937 # position cursor on first line with no password.
2938 first_line = i + 4
2939 f.close()
2940
Tao Bao986ee862018-10-04 15:46:16 -07002941 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002942
2943 return self.ReadFile()
2944
2945 def ReadFile(self):
2946 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002947 if self.pwfile is None:
2948 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002949 try:
2950 f = open(self.pwfile, "r")
2951 for line in f:
2952 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002953 if not line or line[0] == '#':
2954 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002955 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2956 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002957 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002958 else:
2959 result[m.group(2)] = m.group(1)
2960 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002961 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002962 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002963 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002964 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002965
2966
Dan Albert8e0178d2015-01-27 15:53:15 -08002967def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2968 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002969
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002970 # http://b/18015246
2971 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2972 # for files larger than 2GiB. We can work around this by adjusting their
2973 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2974 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2975 # it isn't clear to me exactly what circumstances cause this).
2976 # `zipfile.write()` must be used directly to work around this.
2977 #
2978 # This mess can be avoided if we port to python3.
2979 saved_zip64_limit = zipfile.ZIP64_LIMIT
2980 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2981
Dan Albert8e0178d2015-01-27 15:53:15 -08002982 if compress_type is None:
2983 compress_type = zip_file.compression
2984 if arcname is None:
2985 arcname = filename
2986
2987 saved_stat = os.stat(filename)
2988
2989 try:
2990 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2991 # file to be zipped and reset it when we're done.
2992 os.chmod(filename, perms)
2993
2994 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002995 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2996 # intentional. zip stores datetimes in local time without a time zone
2997 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2998 # in the zip archive.
2999 local_epoch = datetime.datetime.fromtimestamp(0)
3000 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08003001 os.utime(filename, (timestamp, timestamp))
3002
3003 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
3004 finally:
3005 os.chmod(filename, saved_stat.st_mode)
3006 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003007 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08003008
3009
Tao Bao58c1b962015-05-20 09:32:18 -07003010def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07003011 compress_type=None):
3012 """Wrap zipfile.writestr() function to work around the zip64 limit.
3013
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003014 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07003015 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
3016 when calling crc32(bytes).
3017
3018 But it still works fine to write a shorter string into a large zip file.
3019 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
3020 when we know the string won't be too long.
3021 """
3022
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003023 saved_zip64_limit = zipfile.ZIP64_LIMIT
3024 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3025
Tao Baof3282b42015-04-01 11:21:55 -07003026 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
3027 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07003028 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07003029 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07003030 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08003031 else:
Tao Baof3282b42015-04-01 11:21:55 -07003032 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07003033 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
3034 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
3035 # such a case (since
3036 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
3037 # which seems to make more sense. Otherwise the entry will have 0o000 as the
3038 # permission bits. We follow the logic in Python 3 to get consistent
3039 # behavior between using the two versions.
3040 if not zinfo.external_attr:
3041 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07003042
3043 # If compress_type is given, it overrides the value in zinfo.
3044 if compress_type is not None:
3045 zinfo.compress_type = compress_type
3046
Tao Bao58c1b962015-05-20 09:32:18 -07003047 # If perms is given, it has a priority.
3048 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07003049 # If perms doesn't set the file type, mark it as a regular file.
3050 if perms & 0o770000 == 0:
3051 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07003052 zinfo.external_attr = perms << 16
3053
Tao Baof3282b42015-04-01 11:21:55 -07003054 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07003055 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
3056
Dan Albert8b72aef2015-03-23 19:13:21 -07003057 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003058 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07003059
3060
Kelvin Zhang1caead02022-09-23 10:06:03 -07003061def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08003062 """Deletes entries from a ZIP file.
3063
Tao Bao89d7ab22017-12-14 17:05:33 -08003064 Args:
3065 zip_filename: The name of the ZIP file.
3066 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08003067 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07003068 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08003069 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08003070 # If list is empty, nothing to do
3071 if not entries:
3072 return
Wei Li8895f9e2022-10-10 17:13:17 -07003073
3074 with zipfile.ZipFile(zip_filename, 'r') as zin:
3075 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
3076 raise ExternalError(
3077 "Failed to delete zip entries, name not matched: %s" % entries)
3078
3079 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
3080 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08003081 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
3082 for entry in entries:
3083 cmd.append("-x")
3084 cmd.append(entry)
3085 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07003086
Wei Li8895f9e2022-10-10 17:13:17 -07003087 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08003088
3089
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003090def ZipClose(zip_file):
3091 # http://b/18015246
3092 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
3093 # central directory.
3094 saved_zip64_limit = zipfile.ZIP64_LIMIT
3095 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3096
3097 zip_file.close()
3098
3099 zipfile.ZIP64_LIMIT = saved_zip64_limit
3100
3101
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003102class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003103 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003104 self.name = name
3105 self.data = data
3106 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003107 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003108 self.sha1 = sha1(data).hexdigest()
3109
3110 @classmethod
3111 def FromLocalFile(cls, name, diskname):
3112 f = open(diskname, "rb")
3113 data = f.read()
3114 f.close()
3115 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003116
3117 def WriteToTemp(self):
3118 t = tempfile.NamedTemporaryFile()
3119 t.write(self.data)
3120 t.flush()
3121 return t
3122
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003123 def WriteToDir(self, d):
3124 with open(os.path.join(d, self.name), "wb") as fp:
3125 fp.write(self.data)
3126
Geremy Condra36bd3652014-02-06 19:45:10 -08003127 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003128 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003129
Tao Bao76def242017-11-21 09:25:31 -08003130
Tianjie Xu41976c72019-07-03 13:57:01 -07003131# Expose these two classes to support vendor-specific scripts
3132DataImage = images.DataImage
3133EmptyImage = images.EmptyImage
3134
Tao Bao76def242017-11-21 09:25:31 -08003135
Yifan Hongbdb32012020-05-07 12:38:53 -07003136
3137def GetEntryForDevice(fstab, device):
3138 """
3139 Returns:
3140 The first entry in fstab whose device is the given value.
3141 """
3142 if not fstab:
3143 return None
3144 for mount_point in fstab:
3145 if fstab[mount_point].device == device:
3146 return fstab[mount_point]
3147 return None
3148
Kelvin Zhang0876c412020-06-23 15:06:58 -04003149
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003150def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003151 """Parses and converts a PEM-encoded certificate into DER-encoded.
3152
3153 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3154
3155 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003156 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003157 """
3158 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003159 save = False
3160 for line in data.split("\n"):
3161 if "--END CERTIFICATE--" in line:
3162 break
3163 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003164 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003165 if "--BEGIN CERTIFICATE--" in line:
3166 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003167 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003168 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003169
Tao Bao04e1f012018-02-04 12:13:35 -08003170
3171def ExtractPublicKey(cert):
3172 """Extracts the public key (PEM-encoded) from the given certificate file.
3173
3174 Args:
3175 cert: The certificate filename.
3176
3177 Returns:
3178 The public key string.
3179
3180 Raises:
3181 AssertionError: On non-zero return from 'openssl'.
3182 """
3183 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3184 # While openssl 1.1 writes the key into the given filename followed by '-out',
3185 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3186 # stdout instead.
3187 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3188 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3189 pubkey, stderrdata = proc.communicate()
3190 assert proc.returncode == 0, \
3191 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3192 return pubkey
3193
3194
Tao Bao1ac886e2019-06-26 11:58:22 -07003195def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003196 """Extracts the AVB public key from the given public or private key.
3197
3198 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003199 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003200 key: The input key file, which should be PEM-encoded public or private key.
3201
3202 Returns:
3203 The path to the extracted AVB public key file.
3204 """
3205 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3206 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003207 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003208 return output
3209
3210
jiajia tangf3f842b2021-03-17 21:49:44 +08003211def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003212 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003213 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003214
3215 Args:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003216 boot_img: the boot image file. Ramdisk must be compressed with lz4 or gzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003217
3218 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003219 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003220 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003221 tmp_dir = MakeTempDir('boot_', suffix='.img')
3222 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003223 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3224 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003225 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3226 if not os.path.isfile(ramdisk):
3227 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3228 return None
3229 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003230 if ramdisk_format == RamdiskFormat.LZ4:
3231 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3232 elif ramdisk_format == RamdiskFormat.GZ:
3233 with open(ramdisk, 'rb') as input_stream:
3234 with open(uncompressed_ramdisk, 'wb') as output_stream:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003235 p2 = Run(['gzip', '-d'], stdin=input_stream.fileno(),
Kelvin Zhang563750f2021-04-28 12:46:17 -04003236 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003237 p2.wait()
3238 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003239 logger.error('Only support lz4 or gzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08003240 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003241
3242 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3243 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3244 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3245 # the host environment.
3246 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003247 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003248
Yifan Hongc65a0542021-01-07 14:21:01 -08003249 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3250 prop_file = os.path.join(extracted_ramdisk, search_path)
3251 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003252 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003253 logger.warning(
3254 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003255
Yifan Hong7dc51172021-01-12 11:27:39 -08003256 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003257
Yifan Hong85ac5012021-01-07 14:43:46 -08003258 except ExternalError as e:
3259 logger.warning('Unable to get boot image build props: %s', e)
3260 return None
3261
3262
3263def GetBootImageTimestamp(boot_img):
3264 """
3265 Get timestamp from ramdisk within the boot image
3266
3267 Args:
3268 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3269
3270 Return:
3271 An integer that corresponds to the timestamp of the boot image, or None
3272 if file has unknown format. Raise exception if an unexpected error has
3273 occurred.
3274 """
3275 prop_file = GetBootImageBuildProp(boot_img)
3276 if not prop_file:
3277 return None
3278
3279 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3280 if props is None:
3281 return None
3282
3283 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003284 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3285 if timestamp:
3286 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003287 logger.warning(
3288 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003289 return None
3290
3291 except ExternalError as e:
3292 logger.warning('Unable to get boot image timestamp: %s', e)
3293 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003294
3295
Kelvin Zhang26390482021-11-02 14:31:10 -07003296def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07003297 if not os.path.exists(filepath):
3298 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07003299 with open(filepath, 'rb') as fp:
3300 # Magic for android sparse image format
3301 # https://source.android.com/devices/bootloader/images
3302 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07003303
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07003304
Kelvin Zhang22680912023-05-19 13:12:59 -07003305def UnsparseImage(filepath, target_path=None):
3306 if not IsSparseImage(filepath):
3307 return
3308 if target_path is None:
3309 tmp_img = MakeTempFile(suffix=".img")
3310 RunAndCheckOutput(["simg2img", filepath, tmp_img])
3311 os.rename(tmp_img, filepath)
3312 else:
3313 RunAndCheckOutput(["simg2img", filepath, target_path])
3314
3315
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07003316def ParseUpdateEngineConfig(path: str):
3317 """Parse the update_engine config stored in file `path`
3318 Args
3319 path: Path to update_engine_config.txt file in target_files
3320
3321 Returns
3322 A tuple of (major, minor) version number . E.g. (2, 8)
3323 """
3324 with open(path, "r") as fp:
3325 # update_engine_config.txt is only supposed to contain two lines,
3326 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
3327 # sufficient. If the length is more than that, something is wrong.
3328 data = fp.read(1024)
3329 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
3330 if not major:
3331 raise ValueError(
3332 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
3333 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
3334 if not minor:
3335 raise ValueError(
3336 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07003337 return (int(major.group(1)), int(minor.group(1)))