blob: 462c3bf0ff3c791ed6bfc704333269c304ff18af [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):
Kelvin Zhang9f9ac4e2023-11-01 10:12:03 -07001911 print("Using {} as salt for avb footer of {}".format(
1912 filename, partition_name))
Kelvin Zhang160762a2023-10-17 12:27:56 -07001913 with open(path, "rb") as fp:
1914 salt = sha256(fp.read()).hexdigest()
Kelvin Zhang9f9ac4e2023-11-01 10:12:03 -07001915 break
Kelvin Zhang160762a2023-10-17 12:27:56 -07001916 AppendAVBSigningArgs(cmd, partition_name, salt)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001917 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1918 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001919 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1920 cmd.extend(split_args)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001921 RunAndCheckOutput(cmd)
1922
1923
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001924def HasRamdisk(partition_name, info_dict=None):
1925 """Returns true/false to see if a bootable image should have a ramdisk.
1926
1927 Args:
1928 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1929 info_dict: The information dict read from misc_info.txt.
1930 """
1931 if info_dict is None:
1932 info_dict = OPTIONS.info_dict
1933
1934 if partition_name != "boot":
1935 return True # init_boot.img or recovery.img has a ramdisk.
1936
1937 if info_dict.get("recovery_as_boot") == "true":
1938 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1939
Bowgo Tsai85578e02022-04-19 10:50:59 +08001940 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1941 return False # A GKI boot.img has no ramdisk since Android-13.
1942
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001943 if info_dict.get("system_root_image") == "true":
1944 # The ramdisk content is merged into the system.img, so there is NO
1945 # ramdisk in the boot.img or boot-<kernel version>.img.
1946 return False
1947
1948 if info_dict.get("init_boot") == "true":
1949 # The ramdisk is moved to the init_boot.img, so there is NO
1950 # ramdisk in the boot.img or boot-<kernel version>.img.
1951 return False
1952
1953 return True
1954
1955
Doug Zongkerd5131602012-08-02 14:46:42 -07001956def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001957 info_dict=None, two_step_image=False,
1958 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001959 """Return a File object with the desired bootable image.
1960
1961 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1962 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1963 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001964
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001965 if info_dict is None:
1966 info_dict = OPTIONS.info_dict
1967
Doug Zongker55d93282011-01-25 17:03:34 -08001968 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1969 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001970 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001971 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001972
1973 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1974 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001975 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001976 return File.FromLocalFile(name, prebuilt_path)
1977
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001978 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001979 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1980 if os.path.exists(prebuilt_path):
1981 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1982 signed_img = MakeTempFile()
1983 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001984 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1985 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001986
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001987 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001988
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001989 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001990
Doug Zongker6f1d0312014-08-22 08:07:12 -07001991 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001992 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001993 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001994 os.path.join(unpack_dir, 'META/ramdisk_node_list')
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001995 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001996 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001997 if data:
1998 return File(name, data)
1999 return None
Doug Zongker55d93282011-01-25 17:03:34 -08002000
Doug Zongkereef39442009-04-02 12:14:19 -07002001
Lucas Wei03230252022-04-18 16:00:40 +08002002def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07002003 """Build a vendor boot image from the specified sourcedir.
2004
2005 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
2006 turn them into a vendor boot image.
2007
2008 Return the image data, or None if sourcedir does not appear to contains files
2009 for building the requested image.
2010 """
2011
2012 if info_dict is None:
2013 info_dict = OPTIONS.info_dict
2014
2015 img = tempfile.NamedTemporaryFile()
2016
TJ Rhoades6f488e92022-05-01 22:16:22 -07002017 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08002018 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07002019
2020 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
2021 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
2022
2023 cmd = [mkbootimg]
2024
2025 fn = os.path.join(sourcedir, "dtb")
2026 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002027 has_vendor_kernel_boot = (info_dict.get(
2028 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08002029
2030 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
2031 # Otherwise pack dtb into vendor_boot.
2032 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
2033 cmd.append("--dtb")
2034 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07002035
2036 fn = os.path.join(sourcedir, "vendor_cmdline")
2037 if os.access(fn, os.F_OK):
2038 cmd.append("--vendor_cmdline")
2039 cmd.append(open(fn).read().rstrip("\n"))
2040
2041 fn = os.path.join(sourcedir, "base")
2042 if os.access(fn, os.F_OK):
2043 cmd.append("--base")
2044 cmd.append(open(fn).read().rstrip("\n"))
2045
2046 fn = os.path.join(sourcedir, "pagesize")
2047 if os.access(fn, os.F_OK):
2048 cmd.append("--pagesize")
2049 cmd.append(open(fn).read().rstrip("\n"))
2050
2051 args = info_dict.get("mkbootimg_args")
2052 if args and args.strip():
2053 cmd.extend(shlex.split(args))
2054
2055 args = info_dict.get("mkbootimg_version_args")
2056 if args and args.strip():
2057 cmd.extend(shlex.split(args))
2058
2059 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
2060 cmd.extend(["--vendor_boot", img.name])
2061
Devin Moore50509012021-01-13 10:45:04 -08002062 fn = os.path.join(sourcedir, "vendor_bootconfig")
2063 if os.access(fn, os.F_OK):
2064 cmd.append("--vendor_bootconfig")
2065 cmd.append(fn)
2066
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002067 ramdisk_fragment_imgs = []
2068 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
2069 if os.access(fn, os.F_OK):
2070 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
2071 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002072 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2073 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002074 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04002075 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2076 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002077 # Use prebuilt image if found, else create ramdisk from supplied files.
2078 if os.access(fn, os.F_OK):
2079 ramdisk_fragment_pathname = fn
2080 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002081 ramdisk_fragment_root = os.path.join(
2082 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08002083 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
2084 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002085 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
2086 ramdisk_fragment_pathname = ramdisk_fragment_img.name
2087 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
2088
Steve Mucklee1b10862019-07-10 10:49:37 -07002089 RunAndCheckOutput(cmd)
2090
2091 # AVB: if enabled, calculate and add hash.
2092 if info_dict.get("avb_enable") == "true":
2093 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08002094 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07002095 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08002096 "--partition_size", str(part_size), "--partition_name", partition_name]
2097 AppendAVBSigningArgs(cmd, partition_name)
2098 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07002099 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08002100 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
2101 cmd.extend(split_args)
Steve Mucklee1b10862019-07-10 10:49:37 -07002102 RunAndCheckOutput(cmd)
2103
2104 img.seek(os.SEEK_SET, 0)
2105 data = img.read()
2106
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002107 for f in ramdisk_fragment_imgs:
2108 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002109 ramdisk_img.close()
2110 img.close()
2111
2112 return data
2113
2114
2115def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2116 info_dict=None):
2117 """Return a File object with the desired vendor boot image.
2118
2119 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2120 the source files in 'unpack_dir'/'tree_subdir'."""
2121
2122 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2123 if os.path.exists(prebuilt_path):
2124 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2125 return File.FromLocalFile(name, prebuilt_path)
2126
2127 logger.info("building image from target_files %s...", tree_subdir)
2128
2129 if info_dict is None:
2130 info_dict = OPTIONS.info_dict
2131
Kelvin Zhang0876c412020-06-23 15:06:58 -04002132 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002133 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2134 if data:
2135 return File(name, data)
2136 return None
2137
2138
2139def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002140 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002141 """Return a File object with the desired vendor kernel boot image.
2142
2143 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2144 the source files in 'unpack_dir'/'tree_subdir'."""
2145
2146 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2147 if os.path.exists(prebuilt_path):
2148 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2149 return File.FromLocalFile(name, prebuilt_path)
2150
2151 logger.info("building image from target_files %s...", tree_subdir)
2152
2153 if info_dict is None:
2154 info_dict = OPTIONS.info_dict
2155
2156 data = _BuildVendorBootImage(
2157 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002158 if data:
2159 return File(name, data)
2160 return None
2161
2162
Narayan Kamatha07bf042017-08-14 14:49:21 +01002163def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002164 """Gunzips the given gzip compressed file to a given output file."""
2165 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002166 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002167 shutil.copyfileobj(in_file, out_file)
2168
2169
Kelvin Zhange473ce92023-06-21 13:06:59 -07002170def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
2171 # 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
2172 # higher bits of |external_attr| are unix file permission and types
2173 unix_filetype = info.external_attr >> 16
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002174 file_perm = unix_filetype & 0o777
Kelvin Zhange473ce92023-06-21 13:06:59 -07002175
2176 def CheckMask(a, mask):
2177 return (a & mask) == mask
2178
2179 def IsSymlink(a):
2180 return CheckMask(a, stat.S_IFLNK)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002181
2182 def IsDir(a):
2183 return CheckMask(a, stat.S_IFDIR)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002184 # python3.11 zipfile implementation doesn't handle symlink correctly
2185 if not IsSymlink(unix_filetype):
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002186 target = input_zip.extract(info, dirname)
2187 # We want to ensure that the file is at least read/writable by owner and readable by all users
2188 if IsDir(unix_filetype):
2189 os.chmod(target, file_perm | 0o755)
2190 else:
2191 os.chmod(target, file_perm | 0o644)
2192 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002193 if dirname is None:
2194 dirname = os.getcwd()
2195 target = os.path.join(dirname, info.filename)
2196 os.makedirs(os.path.dirname(target), exist_ok=True)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002197 if os.path.exists(target):
2198 os.unlink(target)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002199 os.symlink(input_zip.read(info).decode(), target)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002200 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002201
2202
Tao Bao0ff15de2019-03-20 11:26:06 -07002203def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002204 """Unzips the archive to the given directory.
2205
2206 Args:
2207 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002208 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002209 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2210 archvie. Non-matching patterns will be filtered out. If there's no match
2211 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002212 """
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002213 with zipfile.ZipFile(filename, allowZip64=True, mode="r") as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002214 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002215 entries = input_zip.infolist()
2216 # b/283033491
2217 # Per https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
2218 # In zip64 mode, central directory record's header_offset field might be
2219 # set to 0xFFFFFFFF if header offset is > 2^32. In this case, the extra
2220 # fields will contain an 8 byte little endian integer at offset 20
2221 # to indicate the actual local header offset.
2222 # As of python3.11, python does not handle zip64 central directories
2223 # correctly, so we will manually do the parsing here.
Kelvin Zhang1e774242023-06-17 09:18:15 -07002224
2225 # ZIP64 central directory extra field has two required fields:
2226 # 2 bytes header ID and 2 bytes size field. Thes two require fields have
2227 # a total size of 4 bytes. Then it has three other 8 bytes field, followed
2228 # by a 4 byte disk number field. The last disk number field is not required
2229 # to be present, but if it is present, the total size of extra field will be
2230 # divisible by 8(because 2+2+4+8*n is always going to be multiple of 8)
2231 # Most extra fields are optional, but when they appear, their must appear
2232 # in the order defined by zip64 spec. Since file header offset is the 2nd
2233 # to last field in zip64 spec, it will only be at last 8 bytes or last 12-4
2234 # bytes, depending on whether disk number is present.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002235 for entry in entries:
Kelvin Zhang1e774242023-06-17 09:18:15 -07002236 if entry.header_offset == 0xFFFFFFFF:
2237 if len(entry.extra) % 8 == 0:
2238 entry.header_offset = int.from_bytes(entry.extra[-12:-4], "little")
2239 else:
2240 entry.header_offset = int.from_bytes(entry.extra[-8:], "little")
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002241 if patterns is not None:
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002242 filtered = [info for info in entries if any(
2243 [fnmatch.fnmatch(info.filename, p) for p in patterns])]
Tao Bao0ff15de2019-03-20 11:26:06 -07002244
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002245 # There isn't any matching files. Don't unzip anything.
2246 if not filtered:
2247 return
Kelvin Zhange473ce92023-06-21 13:06:59 -07002248 for info in filtered:
2249 UnzipSingleFile(input_zip, info, dirname)
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002250 else:
Kelvin Zhange473ce92023-06-21 13:06:59 -07002251 for info in entries:
2252 UnzipSingleFile(input_zip, info, dirname)
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002253
2254
Daniel Norman78554ea2021-09-14 10:29:38 -07002255def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002256 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002257
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002258 Args:
2259 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2260 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2261
Daniel Norman78554ea2021-09-14 10:29:38 -07002262 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002263 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002264
Tao Bao1c830bf2017-12-25 10:43:47 -08002265 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002266 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002267 """
Doug Zongkereef39442009-04-02 12:14:19 -07002268
Tao Bao1c830bf2017-12-25 10:43:47 -08002269 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002270 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2271 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002272 UnzipToDir(m.group(1), tmp, patterns)
2273 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002274 filename = m.group(1)
2275 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002276 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002277
Tao Baodba59ee2018-01-09 13:21:02 -08002278 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002279
2280
Yifan Hong8a66a712019-04-04 15:37:57 -07002281def GetUserImage(which, tmpdir, input_zip,
2282 info_dict=None,
2283 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002284 reset_file_map=False):
2285 """Returns an Image object suitable for passing to BlockImageDiff.
2286
2287 This function loads the specified image from the given path. If the specified
2288 image is sparse, it also performs additional processing for OTA purpose. For
2289 example, it always adds block 0 to clobbered blocks list. It also detects
2290 files that cannot be reconstructed from the block list, for whom we should
2291 avoid applying imgdiff.
2292
2293 Args:
2294 which: The partition name.
2295 tmpdir: The directory that contains the prebuilt image and block map file.
2296 input_zip: The target-files ZIP archive.
2297 info_dict: The dict to be looked up for relevant info.
2298 allow_shared_blocks: If image is sparse, whether having shared blocks is
2299 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002300 reset_file_map: If true and image is sparse, reset file map before returning
2301 the image.
2302 Returns:
2303 A Image object. If it is a sparse image and reset_file_map is False, the
2304 image will have file_map info loaded.
2305 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002306 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002307 info_dict = LoadInfoDict(input_zip)
2308
Kelvin Zhang04521282023-03-02 09:42:52 -08002309 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002310
2311 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2312 # shared blocks (i.e. some blocks will show up in multiple files' block
2313 # list). We can only allocate such shared blocks to the first "owner", and
2314 # disable imgdiff for all later occurrences.
2315 if allow_shared_blocks is None:
2316 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2317
2318 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002319 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002320 if reset_file_map:
2321 img.ResetFileMap()
2322 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002323 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002324
2325
hungweichencc9c05d2022-08-23 05:45:42 +00002326def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002327 """Returns a Image object suitable for passing to BlockImageDiff.
2328
2329 This function loads the specified non-sparse image from the given path.
2330
2331 Args:
2332 which: The partition name.
2333 tmpdir: The directory that contains the prebuilt image and block map file.
2334 Returns:
2335 A Image object.
2336 """
2337 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2338 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2339
2340 # The image and map files must have been created prior to calling
2341 # ota_from_target_files.py (since LMP).
2342 assert os.path.exists(path) and os.path.exists(mappath)
2343
hungweichencc9c05d2022-08-23 05:45:42 +00002344 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002345
Yifan Hong8a66a712019-04-04 15:37:57 -07002346
hungweichencc9c05d2022-08-23 05:45:42 +00002347def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002348 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2349
2350 This function loads the specified sparse image from the given path, and
2351 performs additional processing for OTA purpose. For example, it always adds
2352 block 0 to clobbered blocks list. It also detects files that cannot be
2353 reconstructed from the block list, for whom we should avoid applying imgdiff.
2354
2355 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002356 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002357 tmpdir: The directory that contains the prebuilt image and block map file.
2358 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002359 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002360 Returns:
2361 A SparseImage object, with file_map info loaded.
2362 """
Tao Baoc765cca2018-01-31 17:32:40 -08002363 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2364 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2365
2366 # The image and map files must have been created prior to calling
2367 # ota_from_target_files.py (since LMP).
2368 assert os.path.exists(path) and os.path.exists(mappath)
2369
2370 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2371 # it to clobbered_blocks so that it will be written to the target
2372 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2373 clobbered_blocks = "0"
2374
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002375 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002376 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002377
2378 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2379 # if they contain all zeros. We can't reconstruct such a file from its block
2380 # list. Tag such entries accordingly. (Bug: 65213616)
2381 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002382 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002383 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002384 continue
2385
Tom Cherryd14b8952018-08-09 14:26:00 -07002386 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2387 # filename listed in system.map may contain an additional leading slash
2388 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2389 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002390 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002391 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002392 arcname = entry.lstrip('/')
2393 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002394 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002395 else:
2396 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002397
2398 assert arcname in input_zip.namelist(), \
2399 "Failed to find the ZIP entry for {}".format(entry)
2400
Tao Baoc765cca2018-01-31 17:32:40 -08002401 info = input_zip.getinfo(arcname)
2402 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002403
2404 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002405 # image, check the original block list to determine its completeness. Note
2406 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002407 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002408 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002409
Tao Baoc765cca2018-01-31 17:32:40 -08002410 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2411 ranges.extra['incomplete'] = True
2412
2413 return image
2414
2415
Doug Zongkereef39442009-04-02 12:14:19 -07002416def GetKeyPasswords(keylist):
2417 """Given a list of keys, prompt the user to enter passwords for
2418 those which require them. Return a {key: password} dict. password
2419 will be None if the key has no password."""
2420
Doug Zongker8ce7c252009-05-22 13:34:54 -07002421 no_passwords = []
2422 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002423 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002424 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002425
2426 # sorted() can't compare strings to None, so convert Nones to strings
2427 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002428 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002429 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002430 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002431 continue
2432
T.R. Fullhart37e10522013-03-18 10:31:26 -07002433 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002434 "-inform", "DER", "-nocrypt"],
2435 stdin=devnull.fileno(),
2436 stdout=devnull.fileno(),
2437 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002438 p.communicate()
2439 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002440 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002441 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002442 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002443 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2444 "-inform", "DER", "-passin", "pass:"],
2445 stdin=devnull.fileno(),
2446 stdout=devnull.fileno(),
2447 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002448 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002449 if p.returncode == 0:
2450 # Encrypted key with empty string as password.
2451 key_passwords[k] = ''
2452 elif stderr.startswith('Error decrypting key'):
2453 # Definitely encrypted key.
2454 # It would have said "Error reading key" if it didn't parse correctly.
2455 need_passwords.append(k)
2456 else:
2457 # Potentially, a type of key that openssl doesn't understand.
2458 # We'll let the routines in signapk.jar handle it.
2459 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002460 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002461
T.R. Fullhart37e10522013-03-18 10:31:26 -07002462 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002463 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002464 return key_passwords
2465
2466
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002467def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002468 """Gets the minSdkVersion declared in the APK.
2469
Martin Stjernholm58472e82022-01-07 22:08:47 +00002470 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2471 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002472
2473 Args:
2474 apk_name: The APK filename.
2475
2476 Returns:
2477 The parsed SDK version string.
2478
2479 Raises:
2480 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002481 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002482 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002483 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002484 stderr=subprocess.PIPE)
2485 stdoutdata, stderrdata = proc.communicate()
2486 if proc.returncode != 0:
2487 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002488 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2489 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002490
Tao Baof47bf0f2018-03-21 23:28:51 -07002491 for line in stdoutdata.split("\n"):
James Wuc5e321a2023-08-01 17:45:35 +00002492 # Due to ag/24161708, looking for lines such as minSdkVersion:'23',minSdkVersion:'M'
2493 # or sdkVersion:'23', sdkVersion:'M'.
2494 m = re.match(r'(?:minSdkVersion|sdkVersion):\'([^\']*)\'', line)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002495 if m:
2496 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002497 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002498
2499
2500def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002501 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002502
Tao Baof47bf0f2018-03-21 23:28:51 -07002503 If minSdkVersion is set to a codename, it is translated to a number using the
2504 provided map.
2505
2506 Args:
2507 apk_name: The APK filename.
2508
2509 Returns:
2510 The parsed SDK version number.
2511
2512 Raises:
2513 ExternalError: On failing to get the min SDK version number.
2514 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002515 version = GetMinSdkVersion(apk_name)
2516 try:
2517 return int(version)
2518 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002519 # Not a decimal number.
2520 #
2521 # It could be either a straight codename, e.g.
2522 # UpsideDownCake
2523 #
2524 # Or a codename with API fingerprint SHA, e.g.
2525 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2526 #
2527 # Extract the codename and try and map it to a version number.
2528 split = version.split(".")
2529 codename = split[0]
2530 if codename in codename_to_api_level_map:
2531 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002532 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002533 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2534 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002535
2536
2537def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002538 codename_to_api_level_map=None, whole_file=False,
2539 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002540 """Sign the input_name zip/jar/apk, producing output_name. Use the
2541 given key and password (the latter may be None if the key does not
2542 have a password.
2543
Doug Zongker951495f2009-08-14 12:44:19 -07002544 If whole_file is true, use the "-w" option to SignApk to embed a
2545 signature that covers the whole file in the archive comment of the
2546 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002547
2548 min_api_level is the API Level (int) of the oldest platform this file may end
2549 up on. If not specified for an APK, the API Level is obtained by interpreting
2550 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2551
2552 codename_to_api_level_map is needed to translate the codename which may be
2553 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002554
2555 Caller may optionally specify extra args to be passed to SignApk, which
2556 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002557 """
Tao Bao76def242017-11-21 09:25:31 -08002558 if codename_to_api_level_map is None:
2559 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002560 if extra_signapk_args is None:
2561 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002562
Alex Klyubin9667b182015-12-10 13:38:50 -08002563 java_library_path = os.path.join(
2564 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2565
Tao Baoe95540e2016-11-08 12:08:53 -08002566 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2567 ["-Djava.library.path=" + java_library_path,
2568 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002569 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002570 if whole_file:
2571 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002572
2573 min_sdk_version = min_api_level
2574 if min_sdk_version is None:
2575 if not whole_file:
2576 min_sdk_version = GetMinSdkVersionInt(
2577 input_name, codename_to_api_level_map)
2578 if min_sdk_version is not None:
2579 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2580
T.R. Fullhart37e10522013-03-18 10:31:26 -07002581 cmd.extend([key + OPTIONS.public_key_suffix,
2582 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002583 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002584
Tao Bao73dd4f42018-10-04 16:25:33 -07002585 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002586 if password is not None:
2587 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002588 stdoutdata, _ = proc.communicate(password)
2589 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002590 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002591 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002592 proc.returncode, stdoutdata))
2593
Doug Zongkereef39442009-04-02 12:14:19 -07002594
Doug Zongker37974732010-09-16 17:44:38 -07002595def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002596 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002597
Tao Bao9dd909e2017-11-14 11:27:32 -08002598 For non-AVB images, raise exception if the data is too big. Print a warning
2599 if the data is nearing the maximum size.
2600
2601 For AVB images, the actual image size should be identical to the limit.
2602
2603 Args:
2604 data: A string that contains all the data for the partition.
2605 target: The partition name. The ".img" suffix is optional.
2606 info_dict: The dict to be looked up for relevant info.
2607 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002608 if target.endswith(".img"):
2609 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002610 mount_point = "/" + target
2611
Ying Wangf8824af2014-06-03 14:07:27 -07002612 fs_type = None
2613 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002614 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002615 if mount_point == "/userdata":
2616 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002617 p = info_dict["fstab"][mount_point]
2618 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002619 device = p.device
2620 if "/" in device:
2621 device = device[device.rfind("/")+1:]
Kelvin Zhang8c9166a2023-10-31 13:42:15 -07002622 limit = info_dict.get(device + "_size", 0)
2623 if isinstance(limit, str):
2624 limit = int(limit, 0)
Dan Albert8b72aef2015-03-23 19:13:21 -07002625 if not fs_type or not limit:
2626 return
Doug Zongkereef39442009-04-02 12:14:19 -07002627
Andrew Boie0f9aec82012-02-14 09:32:52 -08002628 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002629 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2630 # path.
2631 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2632 if size != limit:
2633 raise ExternalError(
2634 "Mismatching image size for %s: expected %d actual %d" % (
2635 target, limit, size))
2636 else:
2637 pct = float(size) * 100.0 / limit
2638 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2639 if pct >= 99.0:
2640 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002641
2642 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002643 logger.warning("\n WARNING: %s\n", msg)
2644 else:
2645 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002646
2647
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002648def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002649 """Parses the APK certs info from a given target-files zip.
2650
2651 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2652 tuple with the following elements: (1) a dictionary that maps packages to
2653 certs (based on the "certificate" and "private_key" attributes in the file;
2654 (2) a string representing the extension of compressed APKs in the target files
2655 (e.g ".gz", ".bro").
2656
2657 Args:
2658 tf_zip: The input target_files ZipFile (already open).
2659
2660 Returns:
2661 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2662 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2663 no compressed APKs.
2664 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002665 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002666 compressed_extension = None
2667
Tao Bao0f990332017-09-08 19:02:54 -07002668 # META/apkcerts.txt contains the info for _all_ the packages known at build
2669 # time. Filter out the ones that are not installed.
2670 installed_files = set()
2671 for name in tf_zip.namelist():
2672 basename = os.path.basename(name)
2673 if basename:
2674 installed_files.add(basename)
2675
Tao Baoda30cfa2017-12-01 16:19:46 -08002676 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002677 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002678 if not line:
2679 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002680 m = re.match(
2681 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002682 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2683 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002684 line)
2685 if not m:
2686 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002687
Tao Bao818ddf52018-01-05 11:17:34 -08002688 matches = m.groupdict()
2689 cert = matches["CERT"]
2690 privkey = matches["PRIVKEY"]
2691 name = matches["NAME"]
2692 this_compressed_extension = matches["COMPRESSED"]
2693
2694 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2695 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2696 if cert in SPECIAL_CERT_STRINGS and not privkey:
2697 certmap[name] = cert
2698 elif (cert.endswith(OPTIONS.public_key_suffix) and
2699 privkey.endswith(OPTIONS.private_key_suffix) and
2700 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2701 certmap[name] = cert[:-public_key_suffix_len]
2702 else:
2703 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2704
2705 if not this_compressed_extension:
2706 continue
2707
2708 # Only count the installed files.
2709 filename = name + '.' + this_compressed_extension
2710 if filename not in installed_files:
2711 continue
2712
2713 # Make sure that all the values in the compression map have the same
2714 # extension. We don't support multiple compression methods in the same
2715 # system image.
2716 if compressed_extension:
2717 if this_compressed_extension != compressed_extension:
2718 raise ValueError(
2719 "Multiple compressed extensions: {} vs {}".format(
2720 compressed_extension, this_compressed_extension))
2721 else:
2722 compressed_extension = this_compressed_extension
2723
2724 return (certmap,
2725 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002726
2727
Doug Zongkereef39442009-04-02 12:14:19 -07002728COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002729Global options
2730
2731 -p (--path) <dir>
2732 Prepend <dir>/bin to the list of places to search for binaries run by this
2733 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002734
Doug Zongker05d3dea2009-06-22 11:32:31 -07002735 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002736 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002737
Tao Bao30df8b42018-04-23 15:32:53 -07002738 -x (--extra) <key=value>
2739 Add a key/value pair to the 'extras' dict, which device-specific extension
2740 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002741
Doug Zongkereef39442009-04-02 12:14:19 -07002742 -v (--verbose)
2743 Show command lines being executed.
2744
2745 -h (--help)
2746 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002747
2748 --logfile <file>
2749 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002750"""
2751
Kelvin Zhang0876c412020-06-23 15:06:58 -04002752
Doug Zongkereef39442009-04-02 12:14:19 -07002753def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002754 print(docstring.rstrip("\n"))
2755 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002756
2757
2758def ParseOptions(argv,
2759 docstring,
2760 extra_opts="", extra_long_opts=(),
2761 extra_option_handler=None):
2762 """Parse the options in argv and return any arguments that aren't
2763 flags. docstring is the calling module's docstring, to be displayed
2764 for errors and -h. extra_opts and extra_long_opts are for flags
2765 defined by the caller, which are processed by passing them to
2766 extra_option_handler."""
2767
2768 try:
2769 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002770 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002771 ["help", "verbose", "path=", "signapk_path=",
Thiébaud Weksteen62865ca2023-10-18 11:08:47 +11002772 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002773 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002774 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2775 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002776 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002777 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002778 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002779 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002780 sys.exit(2)
2781
Doug Zongkereef39442009-04-02 12:14:19 -07002782 for o, a in opts:
2783 if o in ("-h", "--help"):
2784 Usage(docstring)
2785 sys.exit()
2786 elif o in ("-v", "--verbose"):
2787 OPTIONS.verbose = True
2788 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002789 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002790 elif o in ("--signapk_path",):
2791 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002792 elif o in ("--signapk_shared_library_path",):
2793 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002794 elif o in ("--extra_signapk_args",):
2795 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002796 elif o in ("--aapt2_path",):
2797 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002798 elif o in ("--java_path",):
2799 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002800 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002801 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002802 elif o in ("--android_jar_path",):
2803 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002804 elif o in ("--public_key_suffix",):
2805 OPTIONS.public_key_suffix = a
2806 elif o in ("--private_key_suffix",):
2807 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002808 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002809 raise ValueError(
2810 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002811 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002812 raise ValueError(
2813 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002814 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002815 raise ValueError(
2816 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002817 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002818 raise ValueError(
2819 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002820 elif o in ("-s", "--device_specific"):
2821 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002822 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002823 key, value = a.split("=", 1)
2824 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002825 elif o in ("--logfile",):
2826 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002827 else:
2828 if extra_option_handler is None or not extra_option_handler(o, a):
2829 assert False, "unknown option \"%s\"" % (o,)
2830
Doug Zongker85448772014-09-09 14:59:20 -07002831 if OPTIONS.search_path:
2832 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2833 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002834
2835 return args
2836
2837
Tao Bao4c851b12016-09-19 13:54:38 -07002838def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002839 """Make a temp file and add it to the list of things to be deleted
2840 when Cleanup() is called. Return the filename."""
2841 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2842 os.close(fd)
2843 OPTIONS.tempfiles.append(fn)
2844 return fn
2845
2846
Tao Bao1c830bf2017-12-25 10:43:47 -08002847def MakeTempDir(prefix='tmp', suffix=''):
2848 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2849
2850 Returns:
2851 The absolute pathname of the new directory.
2852 """
2853 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2854 OPTIONS.tempfiles.append(dir_name)
2855 return dir_name
2856
2857
Doug Zongkereef39442009-04-02 12:14:19 -07002858def Cleanup():
2859 for i in OPTIONS.tempfiles:
Kelvin Zhang22680912023-05-19 13:12:59 -07002860 if not os.path.exists(i):
2861 continue
Doug Zongkereef39442009-04-02 12:14:19 -07002862 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002863 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002864 else:
2865 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002866 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002867
2868
2869class PasswordManager(object):
2870 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002871 self.editor = os.getenv("EDITOR")
2872 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002873
2874 def GetPasswords(self, items):
2875 """Get passwords corresponding to each string in 'items',
2876 returning a dict. (The dict may have keys in addition to the
2877 values in 'items'.)
2878
2879 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2880 user edit that file to add more needed passwords. If no editor is
2881 available, or $ANDROID_PW_FILE isn't define, prompts the user
2882 interactively in the ordinary way.
2883 """
2884
2885 current = self.ReadFile()
2886
2887 first = True
2888 while True:
2889 missing = []
2890 for i in items:
2891 if i not in current or not current[i]:
2892 missing.append(i)
2893 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002894 if not missing:
2895 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002896
2897 for i in missing:
2898 current[i] = ""
2899
2900 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002901 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002902 if sys.version_info[0] >= 3:
2903 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002904 answer = raw_input("try to edit again? [y]> ").strip()
2905 if answer and answer[0] not in 'yY':
2906 raise RuntimeError("key passwords unavailable")
2907 first = False
2908
2909 current = self.UpdateAndReadFile(current)
2910
Kelvin Zhang0876c412020-06-23 15:06:58 -04002911 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002912 """Prompt the user to enter a value (password) for each key in
2913 'current' whose value is fales. Returns a new dict with all the
2914 values.
2915 """
2916 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002917 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002918 if v:
2919 result[k] = v
2920 else:
2921 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002922 result[k] = getpass.getpass(
2923 "Enter password for %s key> " % k).strip()
2924 if result[k]:
2925 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002926 return result
2927
2928 def UpdateAndReadFile(self, current):
2929 if not self.editor or not self.pwfile:
2930 return self.PromptResult(current)
2931
2932 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002933 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002934 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2935 f.write("# (Additional spaces are harmless.)\n\n")
2936
2937 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002938 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002939 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002940 f.write("[[[ %s ]]] %s\n" % (v, k))
2941 if not v and first_line is None:
2942 # position cursor on first line with no password.
2943 first_line = i + 4
2944 f.close()
2945
Tao Bao986ee862018-10-04 15:46:16 -07002946 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002947
2948 return self.ReadFile()
2949
2950 def ReadFile(self):
2951 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002952 if self.pwfile is None:
2953 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002954 try:
2955 f = open(self.pwfile, "r")
2956 for line in f:
2957 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002958 if not line or line[0] == '#':
2959 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002960 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2961 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002962 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002963 else:
2964 result[m.group(2)] = m.group(1)
2965 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002966 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002967 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002968 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002969 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002970
2971
Dan Albert8e0178d2015-01-27 15:53:15 -08002972def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2973 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002974
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002975 # http://b/18015246
2976 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2977 # for files larger than 2GiB. We can work around this by adjusting their
2978 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2979 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2980 # it isn't clear to me exactly what circumstances cause this).
2981 # `zipfile.write()` must be used directly to work around this.
2982 #
2983 # This mess can be avoided if we port to python3.
2984 saved_zip64_limit = zipfile.ZIP64_LIMIT
2985 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2986
Dan Albert8e0178d2015-01-27 15:53:15 -08002987 if compress_type is None:
2988 compress_type = zip_file.compression
2989 if arcname is None:
2990 arcname = filename
2991
2992 saved_stat = os.stat(filename)
2993
2994 try:
2995 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2996 # file to be zipped and reset it when we're done.
2997 os.chmod(filename, perms)
2998
2999 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07003000 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
3001 # intentional. zip stores datetimes in local time without a time zone
3002 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
3003 # in the zip archive.
3004 local_epoch = datetime.datetime.fromtimestamp(0)
3005 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08003006 os.utime(filename, (timestamp, timestamp))
3007
3008 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
3009 finally:
3010 os.chmod(filename, saved_stat.st_mode)
3011 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003012 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08003013
3014
Tao Bao58c1b962015-05-20 09:32:18 -07003015def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07003016 compress_type=None):
3017 """Wrap zipfile.writestr() function to work around the zip64 limit.
3018
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003019 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07003020 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
3021 when calling crc32(bytes).
3022
3023 But it still works fine to write a shorter string into a large zip file.
3024 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
3025 when we know the string won't be too long.
3026 """
3027
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003028 saved_zip64_limit = zipfile.ZIP64_LIMIT
3029 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3030
Tao Baof3282b42015-04-01 11:21:55 -07003031 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
3032 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07003033 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07003034 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07003035 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08003036 else:
Tao Baof3282b42015-04-01 11:21:55 -07003037 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07003038 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
3039 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
3040 # such a case (since
3041 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
3042 # which seems to make more sense. Otherwise the entry will have 0o000 as the
3043 # permission bits. We follow the logic in Python 3 to get consistent
3044 # behavior between using the two versions.
3045 if not zinfo.external_attr:
3046 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07003047
3048 # If compress_type is given, it overrides the value in zinfo.
3049 if compress_type is not None:
3050 zinfo.compress_type = compress_type
3051
Tao Bao58c1b962015-05-20 09:32:18 -07003052 # If perms is given, it has a priority.
3053 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07003054 # If perms doesn't set the file type, mark it as a regular file.
3055 if perms & 0o770000 == 0:
3056 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07003057 zinfo.external_attr = perms << 16
3058
Tao Baof3282b42015-04-01 11:21:55 -07003059 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07003060 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
3061
Dan Albert8b72aef2015-03-23 19:13:21 -07003062 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003063 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07003064
3065
Kelvin Zhang1caead02022-09-23 10:06:03 -07003066def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08003067 """Deletes entries from a ZIP file.
3068
Tao Bao89d7ab22017-12-14 17:05:33 -08003069 Args:
3070 zip_filename: The name of the ZIP file.
3071 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08003072 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07003073 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08003074 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08003075 # If list is empty, nothing to do
3076 if not entries:
3077 return
Wei Li8895f9e2022-10-10 17:13:17 -07003078
3079 with zipfile.ZipFile(zip_filename, 'r') as zin:
3080 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
3081 raise ExternalError(
3082 "Failed to delete zip entries, name not matched: %s" % entries)
3083
3084 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
3085 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08003086 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
3087 for entry in entries:
3088 cmd.append("-x")
3089 cmd.append(entry)
3090 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07003091
Wei Li8895f9e2022-10-10 17:13:17 -07003092 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08003093
3094
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003095def ZipClose(zip_file):
3096 # http://b/18015246
3097 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
3098 # central directory.
3099 saved_zip64_limit = zipfile.ZIP64_LIMIT
3100 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3101
3102 zip_file.close()
3103
3104 zipfile.ZIP64_LIMIT = saved_zip64_limit
3105
3106
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003107class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003108 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003109 self.name = name
3110 self.data = data
3111 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003112 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003113 self.sha1 = sha1(data).hexdigest()
3114
3115 @classmethod
3116 def FromLocalFile(cls, name, diskname):
3117 f = open(diskname, "rb")
3118 data = f.read()
3119 f.close()
3120 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003121
3122 def WriteToTemp(self):
3123 t = tempfile.NamedTemporaryFile()
3124 t.write(self.data)
3125 t.flush()
3126 return t
3127
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003128 def WriteToDir(self, d):
3129 with open(os.path.join(d, self.name), "wb") as fp:
3130 fp.write(self.data)
3131
Geremy Condra36bd3652014-02-06 19:45:10 -08003132 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003133 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003134
Tao Bao76def242017-11-21 09:25:31 -08003135
Tianjie Xu41976c72019-07-03 13:57:01 -07003136# Expose these two classes to support vendor-specific scripts
3137DataImage = images.DataImage
3138EmptyImage = images.EmptyImage
3139
Tao Bao76def242017-11-21 09:25:31 -08003140
Yifan Hongbdb32012020-05-07 12:38:53 -07003141
3142def GetEntryForDevice(fstab, device):
3143 """
3144 Returns:
3145 The first entry in fstab whose device is the given value.
3146 """
3147 if not fstab:
3148 return None
3149 for mount_point in fstab:
3150 if fstab[mount_point].device == device:
3151 return fstab[mount_point]
3152 return None
3153
Kelvin Zhang0876c412020-06-23 15:06:58 -04003154
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003155def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003156 """Parses and converts a PEM-encoded certificate into DER-encoded.
3157
3158 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3159
3160 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003161 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003162 """
3163 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003164 save = False
3165 for line in data.split("\n"):
3166 if "--END CERTIFICATE--" in line:
3167 break
3168 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003169 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003170 if "--BEGIN CERTIFICATE--" in line:
3171 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003172 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003173 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003174
Tao Bao04e1f012018-02-04 12:13:35 -08003175
3176def ExtractPublicKey(cert):
3177 """Extracts the public key (PEM-encoded) from the given certificate file.
3178
3179 Args:
3180 cert: The certificate filename.
3181
3182 Returns:
3183 The public key string.
3184
3185 Raises:
3186 AssertionError: On non-zero return from 'openssl'.
3187 """
3188 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3189 # While openssl 1.1 writes the key into the given filename followed by '-out',
3190 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3191 # stdout instead.
3192 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3193 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3194 pubkey, stderrdata = proc.communicate()
3195 assert proc.returncode == 0, \
3196 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3197 return pubkey
3198
3199
Tao Bao1ac886e2019-06-26 11:58:22 -07003200def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003201 """Extracts the AVB public key from the given public or private key.
3202
3203 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003204 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003205 key: The input key file, which should be PEM-encoded public or private key.
3206
3207 Returns:
3208 The path to the extracted AVB public key file.
3209 """
3210 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3211 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003212 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003213 return output
3214
3215
jiajia tangf3f842b2021-03-17 21:49:44 +08003216def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003217 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003218 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003219
3220 Args:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003221 boot_img: the boot image file. Ramdisk must be compressed with lz4 or gzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003222
3223 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003224 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003225 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003226 tmp_dir = MakeTempDir('boot_', suffix='.img')
3227 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003228 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3229 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003230 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3231 if not os.path.isfile(ramdisk):
3232 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3233 return None
3234 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003235 if ramdisk_format == RamdiskFormat.LZ4:
3236 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3237 elif ramdisk_format == RamdiskFormat.GZ:
3238 with open(ramdisk, 'rb') as input_stream:
3239 with open(uncompressed_ramdisk, 'wb') as output_stream:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003240 p2 = Run(['gzip', '-d'], stdin=input_stream.fileno(),
Kelvin Zhang563750f2021-04-28 12:46:17 -04003241 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003242 p2.wait()
3243 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07003244 logger.error('Only support lz4 or gzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08003245 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003246
3247 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3248 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3249 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3250 # the host environment.
3251 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003252 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003253
Yifan Hongc65a0542021-01-07 14:21:01 -08003254 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3255 prop_file = os.path.join(extracted_ramdisk, search_path)
3256 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003257 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003258 logger.warning(
3259 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003260
Yifan Hong7dc51172021-01-12 11:27:39 -08003261 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003262
Yifan Hong85ac5012021-01-07 14:43:46 -08003263 except ExternalError as e:
3264 logger.warning('Unable to get boot image build props: %s', e)
3265 return None
3266
3267
3268def GetBootImageTimestamp(boot_img):
3269 """
3270 Get timestamp from ramdisk within the boot image
3271
3272 Args:
3273 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3274
3275 Return:
3276 An integer that corresponds to the timestamp of the boot image, or None
3277 if file has unknown format. Raise exception if an unexpected error has
3278 occurred.
3279 """
3280 prop_file = GetBootImageBuildProp(boot_img)
3281 if not prop_file:
3282 return None
3283
3284 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3285 if props is None:
3286 return None
3287
3288 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003289 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3290 if timestamp:
3291 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003292 logger.warning(
3293 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003294 return None
3295
3296 except ExternalError as e:
3297 logger.warning('Unable to get boot image timestamp: %s', e)
3298 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003299
3300
Kelvin Zhang26390482021-11-02 14:31:10 -07003301def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07003302 if not os.path.exists(filepath):
3303 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07003304 with open(filepath, 'rb') as fp:
3305 # Magic for android sparse image format
3306 # https://source.android.com/devices/bootloader/images
3307 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07003308
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07003309
Kelvin Zhang22680912023-05-19 13:12:59 -07003310def UnsparseImage(filepath, target_path=None):
3311 if not IsSparseImage(filepath):
3312 return
3313 if target_path is None:
3314 tmp_img = MakeTempFile(suffix=".img")
3315 RunAndCheckOutput(["simg2img", filepath, tmp_img])
3316 os.rename(tmp_img, filepath)
3317 else:
3318 RunAndCheckOutput(["simg2img", filepath, target_path])
3319
3320
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07003321def ParseUpdateEngineConfig(path: str):
3322 """Parse the update_engine config stored in file `path`
3323 Args
3324 path: Path to update_engine_config.txt file in target_files
3325
3326 Returns
3327 A tuple of (major, minor) version number . E.g. (2, 8)
3328 """
3329 with open(path, "r") as fp:
3330 # update_engine_config.txt is only supposed to contain two lines,
3331 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
3332 # sufficient. If the length is more than that, something is wrong.
3333 data = fp.read(1024)
3334 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
3335 if not major:
3336 raise ValueError(
3337 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
3338 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
3339 if not minor:
3340 raise ValueError(
3341 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07003342 return (int(major.group(1)), int(minor.group(1)))