blob: 64970d96b0389f44bbe66e7039fe24dbb1f8348d [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Kelvin Zhang5ef25192022-10-19 11:25:22 -070023from genericpath import isdir
Doug Zongkereef39442009-04-02 12:14:19 -070024import getopt
25import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010026import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070027import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070028import json
29import logging
30import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070031import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080032import platform
Doug Zongkereef39442009-04-02 12:14:19 -070033import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070034import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070035import shutil
36import subprocess
37import sys
Kelvin Zhange473ce92023-06-21 13:06:59 -070038import stat
Doug Zongkereef39442009-04-02 12:14:19 -070039import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070040import threading
41import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070042import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080043from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070044
Tianjie Xu41976c72019-07-03 13:57:01 -070045import images
Kelvin Zhang27324132021-03-22 15:38:38 -040046import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080047import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070048from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070049
Tao Bao32fcdab2018-10-12 10:30:39 -070050logger = logging.getLogger(__name__)
51
Tao Bao986ee862018-10-04 15:46:16 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070054
Dan Albert8b72aef2015-03-23 19:13:21 -070055 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070056 # Set up search path, in order to find framework/ and lib64/. At the time of
57 # running this function, user-supplied search path (`--path`) hasn't been
58 # available. So the value set here is the default, which might be overridden
59 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040060 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070061 if exec_path.endswith('.py'):
62 script_name = os.path.basename(exec_path)
63 # logger hasn't been initialized yet at this point. Use print to output
64 # warnings.
65 print(
66 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 'executable -- build and run `{}` directly.'.format(
68 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070069 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040070 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030071
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080073 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
74 if "ANDROID_HOST_OUT" in os.environ:
75 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080076 self.signapk_shared_library_path = "lib64" # Relative to search_path
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020077 self.sign_sepolicy_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.extra_signapk_args = []
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020079 self.extra_sign_sepolicy_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000080 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070081 self.java_path = "java" # Use the one on the path by default.
Sorin Basca05085832022-09-14 11:33:22 +010082 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080083 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070084 self.public_key_suffix = ".x509.pem"
85 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070086 # use otatools built boot_signer by default
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020099 self.sepolicy_name = 'sepolicy.apex'
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800112# descriptor into vbmeta.img. When adding a new entry here, the
113# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
114# accordingly.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000115AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Lucas Wei03230252022-04-18 16:00:40 +0800116 'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000117 'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800118
Tao Bao08c190f2019-06-03 23:07:58 -0700119# Chained VBMeta partitions.
120AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
121
Tianjie Xu861f4132018-09-12 11:49:33 -0700122# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400123PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700124 'system',
125 'vendor',
126 'product',
127 'system_ext',
128 'odm',
129 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700130 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000131 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhang563750f2021-04-28 12:46:17 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900227def FindHostToolPath(tool_name):
228 """Finds the path to the host tool.
229
230 Args:
231 tool_name: name of the tool to find
232 Returns:
Cole Faust6833d7d2023-08-01 18:00:37 -0700233 path to the tool if found under the same directory as this binary is located at. If not found,
234 tool_name is returned.
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900235 """
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900236 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
237 tool_path = os.path.join(my_dir, tool_name)
238 if os.path.exists(tool_path):
239 return tool_path
240
241 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700242
Kelvin Zhang563750f2021-04-28 12:46:17 -0400243
Tao Bao39451582017-05-04 11:10:47 -0700244def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700245 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700246
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 Args:
248 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700249 verbose: Whether the commands should be shown. Default to the global
250 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700251 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
252 stdin, etc. stdout and stderr will default to subprocess.PIPE and
253 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800254 universal_newlines will default to True, as most of the users in
255 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700256
257 Returns:
258 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700259 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700260 if 'stdout' not in kwargs and 'stderr' not in kwargs:
261 kwargs['stdout'] = subprocess.PIPE
262 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800263 if 'universal_newlines' not in kwargs:
264 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700265
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900266 if args:
267 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700268 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900269 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270
Kelvin Zhang766eea72021-06-03 09:36:08 -0400271 if verbose is None:
272 verbose = OPTIONS.verbose
273
Tao Bao32fcdab2018-10-12 10:30:39 -0700274 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400275 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700277 return subprocess.Popen(args, **kwargs)
278
279
Tao Bao986ee862018-10-04 15:46:16 -0700280def RunAndCheckOutput(args, verbose=None, **kwargs):
281 """Runs the given command and returns the output.
282
283 Args:
284 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700285 verbose: Whether the commands should be shown. Default to the global
286 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700287 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
288 stdin, etc. stdout and stderr will default to subprocess.PIPE and
289 subprocess.STDOUT respectively unless caller specifies any of them.
290
291 Returns:
292 The output string.
293
294 Raises:
295 ExternalError: On non-zero exit from the command.
296 """
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -0800297 if verbose is None:
298 verbose = OPTIONS.verbose
Tao Bao986ee862018-10-04 15:46:16 -0700299 proc = Run(args, verbose=verbose, **kwargs)
300 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800301 if output is None:
302 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700303 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400304 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700305 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700306 if proc.returncode != 0:
307 raise ExternalError(
308 "Failed to run command '{}' (exit code {}):\n{}".format(
309 args, proc.returncode, output))
310 return output
311
312
Tao Baoc765cca2018-01-31 17:32:40 -0800313def RoundUpTo4K(value):
314 rounded_up = value + 4095
315 return rounded_up - (rounded_up % 4096)
316
317
Ying Wang7e6d4e42010-12-13 16:25:36 -0800318def CloseInheritedPipes():
319 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
320 before doing other work."""
321 if platform.system() != "Darwin":
322 return
323 for d in range(3, 1025):
324 try:
325 stat = os.fstat(d)
326 if stat is not None:
327 pipebit = stat[0] & 0x1000
328 if pipebit != 0:
329 os.close(d)
330 except OSError:
331 pass
332
333
Tao Bao1c320f82019-10-04 23:25:12 -0700334class BuildInfo(object):
335 """A class that holds the information for a given build.
336
337 This class wraps up the property querying for a given source or target build.
338 It abstracts away the logic of handling OEM-specific properties, and caches
339 the commonly used properties such as fingerprint.
340
341 There are two types of info dicts: a) build-time info dict, which is generated
342 at build time (i.e. included in a target_files zip); b) OEM info dict that is
343 specified at package generation time (via command line argument
344 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
345 having "oem_fingerprint_properties" in build-time info dict), all the queries
346 would be answered based on build-time info dict only. Otherwise if using
347 OEM-specific properties, some of them will be calculated from two info dicts.
348
349 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800350 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700351
352 Attributes:
353 info_dict: The build-time info dict.
354 is_ab: Whether it's a build that uses A/B OTA.
355 oem_dicts: A list of OEM dicts.
356 oem_props: A list of OEM properties that should be read from OEM dicts; None
357 if the build doesn't use any OEM-specific property.
358 fingerprint: The fingerprint of the build, which would be calculated based
359 on OEM properties if applicable.
360 device: The device name, which could come from OEM dicts if applicable.
361 """
362
363 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
364 "ro.product.manufacturer", "ro.product.model",
365 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700366 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
367 "product", "odm", "vendor", "system_ext", "system"]
368 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
369 "product", "product_services", "odm", "vendor", "system"]
370 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700371
Tianjiefdda51d2021-05-05 14:46:35 -0700372 # The length of vbmeta digest to append to the fingerprint
373 _VBMETA_DIGEST_SIZE_USED = 8
374
375 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700376 """Initializes a BuildInfo instance with the given dicts.
377
378 Note that it only wraps up the given dicts, without making copies.
379
380 Arguments:
381 info_dict: The build-time info dict.
382 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
383 that it always uses the first dict to calculate the fingerprint or the
384 device name. The rest would be used for asserting OEM properties only
385 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700386 use_legacy_id: Use the legacy build id to construct the fingerprint. This
387 is used when we need a BuildInfo class, while the vbmeta digest is
388 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700389
390 Raises:
391 ValueError: On invalid inputs.
392 """
393 self.info_dict = info_dict
394 self.oem_dicts = oem_dicts
395
396 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700397 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700398
Hongguang Chend7c160f2020-05-03 21:24:26 -0700399 # Skip _oem_props if oem_dicts is None to use BuildInfo in
400 # sign_target_files_apks
401 if self.oem_dicts:
402 self._oem_props = info_dict.get("oem_fingerprint_properties")
403 else:
404 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700405
Daniel Normand5fe8622020-01-08 17:01:11 -0800406 def check_fingerprint(fingerprint):
407 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
408 raise ValueError(
409 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
410 "3.2.2. Build Parameters.".format(fingerprint))
411
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800413 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800414 try:
415 fingerprint = self.CalculatePartitionFingerprint(partition)
416 check_fingerprint(fingerprint)
417 self._partition_fingerprints[partition] = fingerprint
418 except ExternalError:
419 continue
420 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800421 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800422 # need a fingerprint when creating the image.
423 self._partition_fingerprints[
424 "system_other"] = self._partition_fingerprints["system"]
425
Tao Bao1c320f82019-10-04 23:25:12 -0700426 # These two should be computed only after setting self._oem_props.
427 self._device = self.GetOemProperty("ro.product.device")
428 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800429 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700430
431 @property
432 def is_ab(self):
433 return self._is_ab
434
435 @property
436 def device(self):
437 return self._device
438
439 @property
440 def fingerprint(self):
441 return self._fingerprint
442
443 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400444 def is_vabc(self):
Kelvin Zhange634bde2023-04-28 23:59:43 -0700445 return self.info_dict.get("virtual_ab_compression") == "true"
Kelvin Zhang563750f2021-04-28 12:46:17 -0400446
447 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700448 def is_android_r(self):
449 system_prop = self.info_dict.get("system.build.prop")
450 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
451
452 @property
David Anderson1c596172023-04-14 16:01:55 -0700453 def vendor_api_level(self):
454 vendor_prop = self.info_dict.get("vendor.build.prop")
455 if not vendor_prop:
456 return -1
457
458 props = [
459 "ro.board.api_level",
460 "ro.board.first_api_level",
461 "ro.product.first_api_level",
462 ]
463 for prop in props:
464 value = vendor_prop.GetProp(prop)
465 try:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700466 return int(value)
David Anderson1c596172023-04-14 16:01:55 -0700467 except:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700468 pass
David Anderson1c596172023-04-14 16:01:55 -0700469 return -1
470
471 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700472 def is_vabc_xor(self):
473 vendor_prop = self.info_dict.get("vendor.build.prop")
474 vabc_xor_enabled = vendor_prop and \
475 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
476 return vabc_xor_enabled
477
478 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400479 def vendor_suppressed_vabc(self):
480 vendor_prop = self.info_dict.get("vendor.build.prop")
481 vabc_suppressed = vendor_prop and \
482 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
483 return vabc_suppressed and vabc_suppressed.lower() == "true"
484
485 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700486 def oem_props(self):
487 return self._oem_props
488
489 def __getitem__(self, key):
490 return self.info_dict[key]
491
492 def __setitem__(self, key, value):
493 self.info_dict[key] = value
494
495 def get(self, key, default=None):
496 return self.info_dict.get(key, default)
497
498 def items(self):
499 return self.info_dict.items()
500
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000501 def _GetRawBuildProp(self, prop, partition):
502 prop_file = '{}.build.prop'.format(
503 partition) if partition else 'build.prop'
504 partition_props = self.info_dict.get(prop_file)
505 if not partition_props:
506 return None
507 return partition_props.GetProp(prop)
508
Daniel Normand5fe8622020-01-08 17:01:11 -0800509 def GetPartitionBuildProp(self, prop, partition):
510 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800511
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000512 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000513 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000514 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800515
Daniel Normand5fe8622020-01-08 17:01:11 -0800516 # If provided a partition for this property, only look within that
517 # partition's build.prop.
518 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800519 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800521 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000522
523 prop_val = self._GetRawBuildProp(prop, partition)
524 if prop_val is not None:
525 return prop_val
526 raise ExternalError("couldn't find %s in %s.build.prop" %
527 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800528
Tao Bao1c320f82019-10-04 23:25:12 -0700529 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800530 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700531 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
532 return self._ResolveRoProductBuildProp(prop)
533
Tianjiefdda51d2021-05-05 14:46:35 -0700534 if prop == "ro.build.id":
535 return self._GetBuildId()
536
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000537 prop_val = self._GetRawBuildProp(prop, None)
538 if prop_val is not None:
539 return prop_val
540
541 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700542
543 def _ResolveRoProductBuildProp(self, prop):
544 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000545 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700546 if prop_val:
547 return prop_val
548
Steven Laver8e2086e2020-04-27 16:26:31 -0700549 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000550 source_order_val = self._GetRawBuildProp(
551 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700552 if source_order_val:
553 source_order = source_order_val.split(",")
554 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700555 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700556
557 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700558 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700559 raise ExternalError(
560 "Invalid ro.product.property_source_order '{}'".format(source_order))
561
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000562 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700563 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000564 "ro.product", "ro.product.{}".format(source_partition), 1)
565 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700566 if prop_val:
567 return prop_val
568
569 raise ExternalError("couldn't resolve {}".format(prop))
570
Steven Laver8e2086e2020-04-27 16:26:31 -0700571 def _GetRoProductPropsDefaultSourceOrder(self):
572 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
573 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000574 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700575 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000576 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700577 if android_version == "10":
578 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
579 # NOTE: float() conversion of android_version will have rounding error.
580 # We are checking for "9" or less, and using "< 10" is well outside of
581 # possible floating point rounding.
582 try:
583 android_version_val = float(android_version)
584 except ValueError:
585 android_version_val = 0
586 if android_version_val < 10:
587 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
588 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
589
Tianjieb37c5be2020-10-15 21:27:10 -0700590 def _GetPlatformVersion(self):
591 version_sdk = self.GetBuildProp("ro.build.version.sdk")
592 # init code switches to version_release_or_codename (see b/158483506). After
593 # API finalization, release_or_codename will be the same as release. This
594 # is the best effort to support pre-S dev stage builds.
595 if int(version_sdk) >= 30:
596 try:
597 return self.GetBuildProp("ro.build.version.release_or_codename")
598 except ExternalError:
599 logger.warning('Failed to find ro.build.version.release_or_codename')
600
601 return self.GetBuildProp("ro.build.version.release")
602
Tianjiefdda51d2021-05-05 14:46:35 -0700603 def _GetBuildId(self):
604 build_id = self._GetRawBuildProp("ro.build.id", None)
605 if build_id:
606 return build_id
607
608 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
609 if not legacy_build_id:
610 raise ExternalError("Couldn't find build id in property file")
611
612 if self.use_legacy_id:
613 return legacy_build_id
614
615 # Append the top 8 chars of vbmeta digest to the existing build id. The
616 # logic needs to match the one in init, so that OTA can deliver correctly.
617 avb_enable = self.info_dict.get("avb_enable") == "true"
618 if not avb_enable:
619 raise ExternalError("AVB isn't enabled when using legacy build id")
620
621 vbmeta_digest = self.info_dict.get("vbmeta_digest")
622 if not vbmeta_digest:
623 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
624 " id")
625 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
626 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
627
628 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
629 return legacy_build_id + '.' + digest_prefix
630
Tianjieb37c5be2020-10-15 21:27:10 -0700631 def _GetPartitionPlatformVersion(self, partition):
632 try:
633 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
634 partition)
635 except ExternalError:
636 return self.GetPartitionBuildProp("ro.build.version.release",
637 partition)
638
Tao Bao1c320f82019-10-04 23:25:12 -0700639 def GetOemProperty(self, key):
640 if self.oem_props is not None and key in self.oem_props:
641 return self.oem_dicts[0][key]
642 return self.GetBuildProp(key)
643
Daniel Normand5fe8622020-01-08 17:01:11 -0800644 def GetPartitionFingerprint(self, partition):
645 return self._partition_fingerprints.get(partition, None)
646
647 def CalculatePartitionFingerprint(self, partition):
648 try:
649 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
650 except ExternalError:
651 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
652 self.GetPartitionBuildProp("ro.product.brand", partition),
653 self.GetPartitionBuildProp("ro.product.name", partition),
654 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700655 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800656 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400657 self.GetPartitionBuildProp(
658 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800659 self.GetPartitionBuildProp("ro.build.type", partition),
660 self.GetPartitionBuildProp("ro.build.tags", partition))
661
Tao Bao1c320f82019-10-04 23:25:12 -0700662 def CalculateFingerprint(self):
663 if self.oem_props is None:
664 try:
665 return self.GetBuildProp("ro.build.fingerprint")
666 except ExternalError:
667 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
668 self.GetBuildProp("ro.product.brand"),
669 self.GetBuildProp("ro.product.name"),
670 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700671 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700672 self.GetBuildProp("ro.build.id"),
673 self.GetBuildProp("ro.build.version.incremental"),
674 self.GetBuildProp("ro.build.type"),
675 self.GetBuildProp("ro.build.tags"))
676 return "%s/%s/%s:%s" % (
677 self.GetOemProperty("ro.product.brand"),
678 self.GetOemProperty("ro.product.name"),
679 self.GetOemProperty("ro.product.device"),
680 self.GetBuildProp("ro.build.thumbprint"))
681
682 def WriteMountOemScript(self, script):
683 assert self.oem_props is not None
684 recovery_mount_options = self.info_dict.get("recovery_mount_options")
685 script.Mount("/oem", recovery_mount_options)
686
687 def WriteDeviceAssertions(self, script, oem_no_mount):
688 # Read the property directly if not using OEM properties.
689 if not self.oem_props:
690 script.AssertDevice(self.device)
691 return
692
693 # Otherwise assert OEM properties.
694 if not self.oem_dicts:
695 raise ExternalError(
696 "No OEM file provided to answer expected assertions")
697
698 for prop in self.oem_props.split():
699 values = []
700 for oem_dict in self.oem_dicts:
701 if prop in oem_dict:
702 values.append(oem_dict[prop])
703 if not values:
704 raise ExternalError(
705 "The OEM file is missing the property %s" % (prop,))
706 script.AssertOemProperty(prop, values, oem_no_mount)
707
708
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700709def DoesInputFileContain(input_file, fn):
710 """Check whether the input target_files.zip contain an entry `fn`"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000711 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700712 return fn in input_file.namelist()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700713 elif zipfile.is_zipfile(input_file):
714 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700715 return fn in zfp.namelist()
716 else:
717 if not os.path.isdir(input_file):
718 raise ValueError(
719 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
720 path = os.path.join(input_file, *fn.split("/"))
721 return os.path.exists(path)
722
723
724def ReadBytesFromInputFile(input_file, fn):
725 """Reads the bytes of fn from input zipfile or directory."""
726 if isinstance(input_file, zipfile.ZipFile):
727 return input_file.read(fn)
728 elif zipfile.is_zipfile(input_file):
729 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
730 return zfp.read(fn)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000731 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700732 if not os.path.isdir(input_file):
733 raise ValueError(
734 "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 +0000735 path = os.path.join(input_file, *fn.split("/"))
736 try:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700737 with open(path, "rb") as f:
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000738 return f.read()
739 except IOError as e:
740 if e.errno == errno.ENOENT:
741 raise KeyError(fn)
742
743
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700744def ReadFromInputFile(input_file, fn):
745 """Reads the str contents of fn from input zipfile or directory."""
746 return ReadBytesFromInputFile(input_file, fn).decode()
747
748
Kelvin Zhang6b10e152023-05-02 15:48:16 -0700749def WriteBytesToInputFile(input_file, fn, data):
750 """Write bytes |data| contents to fn of input zipfile or directory."""
751 if isinstance(input_file, zipfile.ZipFile):
752 with input_file.open(fn, "w") as entry_fp:
753 return entry_fp.write(data)
754 elif zipfile.is_zipfile(input_file):
755 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
756 with zfp.open(fn, "w") as entry_fp:
757 return entry_fp.write(data)
758 else:
759 if not os.path.isdir(input_file):
760 raise ValueError(
761 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
762 path = os.path.join(input_file, *fn.split("/"))
763 try:
764 with open(path, "wb") as f:
765 return f.write(data)
766 except IOError as e:
767 if e.errno == errno.ENOENT:
768 raise KeyError(fn)
769
770
771def WriteToInputFile(input_file, fn, str: str):
772 """Write str content to fn of input file or directory"""
773 return WriteBytesToInputFile(input_file, fn, str.encode())
774
775
Yifan Hong10482a22021-01-07 14:38:41 -0800776def ExtractFromInputFile(input_file, fn):
777 """Extracts the contents of fn from input zipfile or directory into a file."""
778 if isinstance(input_file, zipfile.ZipFile):
779 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500780 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800781 f.write(input_file.read(fn))
782 return tmp_file
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700783 elif zipfile.is_zipfile(input_file):
784 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
785 tmp_file = MakeTempFile(os.path.basename(fn))
786 with open(tmp_file, "wb") as fp:
787 fp.write(zfp.read(fn))
788 return tmp_file
Yifan Hong10482a22021-01-07 14:38:41 -0800789 else:
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700790 if not os.path.isdir(input_file):
791 raise ValueError(
792 "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 -0800793 file = os.path.join(input_file, *fn.split("/"))
794 if not os.path.exists(file):
795 raise KeyError(fn)
796 return file
797
Kelvin Zhang563750f2021-04-28 12:46:17 -0400798
jiajia tangf3f842b2021-03-17 21:49:44 +0800799class RamdiskFormat(object):
800 LZ4 = 1
801 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800802
Kelvin Zhang563750f2021-04-28 12:46:17 -0400803
TJ Rhoades6f488e92022-05-01 22:16:22 -0700804def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800805 if info_dict.get('lz4_ramdisks') == 'true':
806 ramdisk_format = RamdiskFormat.LZ4
807 else:
808 ramdisk_format = RamdiskFormat.GZ
809 return ramdisk_format
810
Kelvin Zhang563750f2021-04-28 12:46:17 -0400811
Tao Bao410ad8b2018-08-24 12:08:38 -0700812def LoadInfoDict(input_file, repacking=False):
813 """Loads the key/value pairs from the given input target_files.
814
Tianjiea85bdf02020-07-29 11:56:19 -0700815 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700816 checks and returns the parsed key/value pairs for to the given build. It's
817 usually called early when working on input target_files files, e.g. when
818 generating OTAs, or signing builds. Note that the function may be called
819 against an old target_files file (i.e. from past dessert releases). So the
820 property parsing needs to be backward compatible.
821
822 In a `META/misc_info.txt`, a few properties are stored as links to the files
823 in the PRODUCT_OUT directory. It works fine with the build system. However,
824 they are no longer available when (re)generating images from target_files zip.
825 When `repacking` is True, redirect these properties to the actual files in the
826 unzipped directory.
827
828 Args:
829 input_file: The input target_files file, which could be an open
830 zipfile.ZipFile instance, or a str for the dir that contains the files
831 unzipped from a target_files file.
832 repacking: Whether it's trying repack an target_files file after loading the
833 info dict (default: False). If so, it will rewrite a few loaded
834 properties (e.g. selinux_fc, root_dir) to point to the actual files in
835 target_files file. When doing repacking, `input_file` must be a dir.
836
837 Returns:
838 A dict that contains the parsed key/value pairs.
839
840 Raises:
841 AssertionError: On invalid input arguments.
842 ValueError: On malformed input values.
843 """
844 if repacking:
845 assert isinstance(input_file, str), \
846 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700847
Doug Zongkerc9253822014-02-04 12:17:58 -0800848 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000849 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800850
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700851 try:
Michael Runge6e836112014-04-15 17:40:21 -0700852 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700853 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700854 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700855
Tao Bao410ad8b2018-08-24 12:08:38 -0700856 if "recovery_api_version" not in d:
857 raise ValueError("Failed to find 'recovery_api_version'")
858 if "fstab_version" not in d:
859 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800860
Tao Bao410ad8b2018-08-24 12:08:38 -0700861 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700862 # "selinux_fc" properties should point to the file_contexts files
863 # (file_contexts.bin) under META/.
864 for key in d:
865 if key.endswith("selinux_fc"):
866 fc_basename = os.path.basename(d[key])
867 fc_config = os.path.join(input_file, "META", fc_basename)
868 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700869
Daniel Norman72c626f2019-05-13 15:58:14 -0700870 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700871
Tom Cherryd14b8952018-08-09 14:26:00 -0700872 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700873 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700874 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700875 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700876
David Anderson0ec64ac2019-12-06 12:21:18 -0800877 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700878 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000879 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800880 key_name = part_name + "_base_fs_file"
881 if key_name not in d:
882 continue
883 basename = os.path.basename(d[key_name])
884 base_fs_file = os.path.join(input_file, "META", basename)
885 if os.path.exists(base_fs_file):
886 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700887 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700888 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800889 "Failed to find %s base fs file: %s", part_name, base_fs_file)
890 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700891
Doug Zongker37974732010-09-16 17:44:38 -0700892 def makeint(key):
893 if key in d:
894 d[key] = int(d[key], 0)
895
896 makeint("recovery_api_version")
897 makeint("blocksize")
898 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700899 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700900 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700901 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700902 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800903 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700904
Steve Muckle903a1ca2020-05-07 17:32:10 -0700905 boot_images = "boot.img"
906 if "boot_images" in d:
907 boot_images = d["boot_images"]
908 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400909 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700910
Tao Bao765668f2019-10-04 22:03:00 -0700911 # Load recovery fstab if applicable.
912 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700913 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800914
Tianjie Xu861f4132018-09-12 11:49:33 -0700915 # Tries to load the build props for all partitions with care_map, including
916 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800917 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800918 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000919 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800920 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700921 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800922
Tao Bao12d87fc2018-01-31 12:18:52 -0800923 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700924 # Set the vbmeta digest if exists
925 try:
926 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
927 except KeyError:
928 pass
929
Kelvin Zhang39aea442020-08-17 11:04:25 -0400930 try:
931 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
932 except KeyError:
933 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700934 return d
935
Tao Baod1de6f32017-03-01 16:38:48 -0800936
Daniel Norman4cc9df62019-07-18 10:11:07 -0700937def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900938 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700939 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900940
Daniel Norman4cc9df62019-07-18 10:11:07 -0700941
942def LoadDictionaryFromFile(file_path):
943 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900944 return LoadDictionaryFromLines(lines)
945
946
Michael Runge6e836112014-04-15 17:40:21 -0700947def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700948 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700949 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700950 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700951 if not line or line.startswith("#"):
952 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700953 if "=" in line:
954 name, value = line.split("=", 1)
955 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700956 return d
957
Tao Baod1de6f32017-03-01 16:38:48 -0800958
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000959class PartitionBuildProps(object):
960 """The class holds the build prop of a particular partition.
961
962 This class loads the build.prop and holds the build properties for a given
963 partition. It also partially recognizes the 'import' statement in the
964 build.prop; and calculates alternative values of some specific build
965 properties during runtime.
966
967 Attributes:
968 input_file: a zipped target-file or an unzipped target-file directory.
969 partition: name of the partition.
970 props_allow_override: a list of build properties to search for the
971 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000972 build_props: a dict of build properties for the given partition.
973 prop_overrides: a set of props that are overridden by import.
974 placeholder_values: A dict of runtime variables' values to replace the
975 placeholders in the build.prop file. We expect exactly one value for
976 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800977 ramdisk_format: If name is "boot", the format of ramdisk inside the
978 boot image. Otherwise, its value is ignored.
Elliott Hughes97ad1202023-06-20 16:41:58 -0700979 Use lz4 to decompress by default. If its value is gzip, use gzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000980 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400981
Tianjie Xu9afb2212020-05-10 21:48:15 +0000982 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000983 self.input_file = input_file
984 self.partition = name
985 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000986 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000987 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000988 self.prop_overrides = set()
989 self.placeholder_values = {}
990 if placeholder_values:
991 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000992
993 @staticmethod
994 def FromDictionary(name, build_props):
995 """Constructs an instance from a build prop dictionary."""
996
997 props = PartitionBuildProps("unknown", name)
998 props.build_props = build_props.copy()
999 return props
1000
1001 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +08001002 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001003 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -08001004
Devin Mooreafdd7c72021-12-13 22:04:08 +00001005 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001006 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +00001007 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001008 else:
1009 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
1010
1011 props = PartitionBuildProps(input_file, name, placeholder_values)
1012 props._LoadBuildProp(data)
1013 return props
1014
1015 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +00001016 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -08001017 """
1018 Read build.prop for boot image from input_file.
1019 Return empty string if not found.
1020 """
Devin Mooreafdd7c72021-12-13 22:04:08 +00001021 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -08001022 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001023 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001024 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001025 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001026 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +08001027 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001028 if prop_file is None:
1029 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -05001030 with open(prop_file, "r") as f:
1031 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -08001032
1033 @staticmethod
1034 def _ReadPartitionPropFile(input_file, name):
1035 """
1036 Read build.prop for name from input_file.
1037 Return empty string if not found.
1038 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001039 data = ''
1040 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
1041 '{}/build.prop'.format(name.upper())]:
1042 try:
1043 data = ReadFromInputFile(input_file, prop_file)
1044 break
1045 except KeyError:
1046 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -08001047 if data == '':
1048 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -08001049 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001050
Yifan Hong125d0b62020-09-24 17:07:03 -07001051 @staticmethod
1052 def FromBuildPropFile(name, build_prop_file):
1053 """Constructs an instance from a build prop file."""
1054
1055 props = PartitionBuildProps("unknown", name)
1056 with open(build_prop_file) as f:
1057 props._LoadBuildProp(f.read())
1058 return props
1059
Tianjie Xu9afb2212020-05-10 21:48:15 +00001060 def _LoadBuildProp(self, data):
1061 for line in data.split('\n'):
1062 line = line.strip()
1063 if not line or line.startswith("#"):
1064 continue
1065 if line.startswith("import"):
1066 overrides = self._ImportParser(line)
1067 duplicates = self.prop_overrides.intersection(overrides.keys())
1068 if duplicates:
1069 raise ValueError('prop {} is overridden multiple times'.format(
1070 ','.join(duplicates)))
1071 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1072 self.build_props.update(overrides)
1073 elif "=" in line:
1074 name, value = line.split("=", 1)
1075 if name in self.prop_overrides:
1076 raise ValueError('prop {} is set again after overridden by import '
1077 'statement'.format(name))
1078 self.build_props[name] = value
1079
1080 def _ImportParser(self, line):
1081 """Parses the build prop in a given import statement."""
1082
1083 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001084 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001085 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001086
1087 if len(tokens) == 3:
1088 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1089 return {}
1090
Tianjie Xu9afb2212020-05-10 21:48:15 +00001091 import_path = tokens[1]
1092 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001093 logger.warn('Unrecognized import path {}'.format(line))
1094 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001095
1096 # We only recognize a subset of import statement that the init process
1097 # supports. And we can loose the restriction based on how the dynamic
1098 # fingerprint is used in practice. The placeholder format should be
1099 # ${placeholder}, and its value should be provided by the caller through
1100 # the placeholder_values.
1101 for prop, value in self.placeholder_values.items():
1102 prop_place_holder = '${{{}}}'.format(prop)
1103 if prop_place_holder in import_path:
1104 import_path = import_path.replace(prop_place_holder, value)
1105 if '$' in import_path:
1106 logger.info('Unresolved place holder in import path %s', import_path)
1107 return {}
1108
1109 import_path = import_path.replace('/{}'.format(self.partition),
1110 self.partition.upper())
1111 logger.info('Parsing build props override from %s', import_path)
1112
1113 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1114 d = LoadDictionaryFromLines(lines)
1115 return {key: val for key, val in d.items()
1116 if key in self.props_allow_override}
1117
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001118 def __getstate__(self):
1119 state = self.__dict__.copy()
1120 # Don't pickle baz
1121 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1122 state["input_file"] = state["input_file"].filename
1123 return state
1124
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001125 def GetProp(self, prop):
1126 return self.build_props.get(prop)
1127
1128
Tianjie Xucfa86222016-03-07 16:31:19 -08001129def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1130 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001131 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001132 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 self.mount_point = mount_point
1134 self.fs_type = fs_type
1135 self.device = device
1136 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001137 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001138 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001139
1140 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001141 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001142 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001143 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001144 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001145
Tao Baod1de6f32017-03-01 16:38:48 -08001146 assert fstab_version == 2
1147
1148 d = {}
1149 for line in data.split("\n"):
1150 line = line.strip()
1151 if not line or line.startswith("#"):
1152 continue
1153
1154 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1155 pieces = line.split()
1156 if len(pieces) != 5:
1157 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1158
1159 # Ignore entries that are managed by vold.
1160 options = pieces[4]
1161 if "voldmanaged=" in options:
1162 continue
1163
1164 # It's a good line, parse it.
1165 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001166 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001167 options = options.split(",")
1168 for i in options:
1169 if i.startswith("length="):
1170 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001171 elif i == "slotselect":
1172 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001173 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001174 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001175 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001176
Tao Baod1de6f32017-03-01 16:38:48 -08001177 mount_flags = pieces[3]
1178 # Honor the SELinux context if present.
1179 context = None
1180 for i in mount_flags.split(","):
1181 if i.startswith("context="):
1182 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001183
Tao Baod1de6f32017-03-01 16:38:48 -08001184 mount_point = pieces[1]
1185 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001186 device=pieces[0], length=length, context=context,
1187 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001188
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001189 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001190 # system. Other areas assume system is always at "/system" so point /system
1191 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001192 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001193 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001194 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001195 return d
1196
1197
Tao Bao765668f2019-10-04 22:03:00 -07001198def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1199 """Finds the path to recovery fstab and loads its contents."""
1200 # recovery fstab is only meaningful when installing an update via recovery
1201 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001202 if info_dict.get('ab_update') == 'true' and \
1203 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001204 return None
1205
1206 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1207 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1208 # cases, since it may load the info_dict from an old build (e.g. when
1209 # generating incremental OTAs from that build).
1210 system_root_image = info_dict.get('system_root_image') == 'true'
1211 if info_dict.get('no_recovery') != 'true':
1212 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1213 if isinstance(input_file, zipfile.ZipFile):
1214 if recovery_fstab_path not in input_file.namelist():
1215 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1216 else:
1217 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1218 if not os.path.exists(path):
1219 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1220 return LoadRecoveryFSTab(
1221 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1222 system_root_image)
1223
1224 if info_dict.get('recovery_as_boot') == 'true':
1225 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1226 if isinstance(input_file, zipfile.ZipFile):
1227 if recovery_fstab_path not in input_file.namelist():
1228 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1229 else:
1230 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1231 if not os.path.exists(path):
1232 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1233 return LoadRecoveryFSTab(
1234 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1235 system_root_image)
1236
1237 return None
1238
1239
Doug Zongker37974732010-09-16 17:44:38 -07001240def DumpInfoDict(d):
1241 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001242 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001243
Dan Albert8b72aef2015-03-23 19:13:21 -07001244
Daniel Norman55417142019-11-25 16:04:36 -08001245def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001246 """Merges dynamic partition info variables.
1247
1248 Args:
1249 framework_dict: The dictionary of dynamic partition info variables from the
1250 partial framework target files.
1251 vendor_dict: The dictionary of dynamic partition info variables from the
1252 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001253
1254 Returns:
1255 The merged dynamic partition info dictionary.
1256 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001257
1258 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001259 combined = set(a.split())
1260 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001261 combined = [item.strip() for item in combined if item.strip()]
1262 return " ".join(sorted(combined))
1263
1264 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001265 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001266 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1267
1268 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001269 # For keys-value pairs that are the same, copy to merged dict
1270 for key in vendor_dict.keys():
1271 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1272 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001273
1274 merged_dict["dynamic_partition_list"] = uniq_concat(
1275 framework_dict.get("dynamic_partition_list", ""),
1276 vendor_dict.get("dynamic_partition_list", ""))
1277
1278 # Super block devices are defined by the vendor dict.
1279 if "super_block_devices" in vendor_dict:
1280 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001281 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001282 key = "super_%s_device_size" % block_device
1283 if key not in vendor_dict:
1284 raise ValueError("Vendor dict does not contain required key %s." % key)
1285 merged_dict[key] = vendor_dict[key]
1286
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001287 # Partition groups and group sizes are defined by the vendor dict because
1288 # these values may vary for each board that uses a shared system image.
1289 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001290 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001291 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001292 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001293 if key not in vendor_dict:
1294 raise ValueError("Vendor dict does not contain required key %s." % key)
1295 merged_dict[key] = vendor_dict[key]
1296
1297 # Set the partition group's partition list using a concatenation of the
1298 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001299 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001300 merged_dict[key] = uniq_concat(
1301 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301302
Daniel Normanb0c75912020-09-24 14:30:21 -07001303 # Various other flags should be copied from the vendor dict, if defined.
1304 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1305 "super_metadata_device", "super_partition_error_limit",
1306 "super_partition_size"):
1307 if key in vendor_dict.keys():
1308 merged_dict[key] = vendor_dict[key]
1309
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001310 return merged_dict
1311
1312
Daniel Norman21c34f72020-11-11 17:25:50 -08001313def PartitionMapFromTargetFiles(target_files_dir):
1314 """Builds a map from partition -> path within an extracted target files directory."""
1315 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1316 possible_subdirs = {
1317 "system": ["SYSTEM"],
1318 "vendor": ["VENDOR", "SYSTEM/vendor"],
1319 "product": ["PRODUCT", "SYSTEM/product"],
1320 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1321 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1322 "vendor_dlkm": [
1323 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1324 ],
1325 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001326 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001327 }
1328 partition_map = {}
1329 for partition, subdirs in possible_subdirs.items():
1330 for subdir in subdirs:
1331 if os.path.exists(os.path.join(target_files_dir, subdir)):
1332 partition_map[partition] = subdir
1333 break
1334 return partition_map
1335
1336
Daniel Normand3351562020-10-29 12:33:11 -07001337def SharedUidPartitionViolations(uid_dict, partition_groups):
1338 """Checks for APK sharedUserIds that cross partition group boundaries.
1339
1340 This uses a single or merged build's shareduid_violation_modules.json
1341 output file, as generated by find_shareduid_violation.py or
1342 core/tasks/find-shareduid-violation.mk.
1343
1344 An error is defined as a sharedUserId that is found in a set of partitions
1345 that span more than one partition group.
1346
1347 Args:
1348 uid_dict: A dictionary created by using the standard json module to read a
1349 complete shareduid_violation_modules.json file.
1350 partition_groups: A list of groups, where each group is a list of
1351 partitions.
1352
1353 Returns:
1354 A list of error messages.
1355 """
1356 errors = []
1357 for uid, partitions in uid_dict.items():
1358 found_in_groups = [
1359 group for group in partition_groups
1360 if set(partitions.keys()) & set(group)
1361 ]
1362 if len(found_in_groups) > 1:
1363 errors.append(
1364 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1365 % (uid, ",".join(sorted(partitions.keys()))))
1366 return errors
1367
1368
Daniel Norman21c34f72020-11-11 17:25:50 -08001369def RunHostInitVerifier(product_out, partition_map):
1370 """Runs host_init_verifier on the init rc files within partitions.
1371
1372 host_init_verifier searches the etc/init path within each partition.
1373
1374 Args:
1375 product_out: PRODUCT_OUT directory, containing partition directories.
1376 partition_map: A map of partition name -> relative path within product_out.
1377 """
1378 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1379 cmd = ["host_init_verifier"]
1380 for partition, path in partition_map.items():
1381 if partition not in allowed_partitions:
1382 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1383 partition)
1384 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1385 # Add --property-contexts if the file exists on the partition.
1386 property_contexts = "%s_property_contexts" % (
1387 "plat" if partition == "system" else partition)
1388 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1389 property_contexts)
1390 if os.path.exists(property_contexts_path):
1391 cmd.append("--property-contexts=%s" % property_contexts_path)
1392 # Add the passwd file if the file exists on the partition.
1393 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1394 if os.path.exists(passwd_path):
1395 cmd.extend(["-p", passwd_path])
1396 return RunAndCheckOutput(cmd)
1397
1398
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001399def AppendAVBSigningArgs(cmd, partition):
1400 """Append signing arguments for avbtool."""
1401 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
Kelvin Zhange634bde2023-04-28 23:59:43 -07001402 key_path = ResolveAVBSigningPathArgs(
1403 OPTIONS.info_dict.get("avb_" + partition + "_key_path"))
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001404 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1405 if key_path and algorithm:
1406 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001407 avb_salt = OPTIONS.info_dict.get("avb_salt")
1408 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001409 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001410 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001411
1412
zhangyongpeng70756972023-04-12 15:31:33 +08001413def ResolveAVBSigningPathArgs(split_args):
1414
1415 def ResolveBinaryPath(path):
1416 if os.path.exists(path):
1417 return path
Kelvin Zhang97a5afe2023-06-27 10:30:48 -07001418 if OPTIONS.search_path:
1419 new_path = os.path.join(OPTIONS.search_path, path)
1420 if os.path.exists(new_path):
1421 return new_path
zhangyongpeng70756972023-04-12 15:31:33 +08001422 raise ExternalError(
Kelvin Zhang43df0802023-07-24 13:16:03 -07001423 "Failed to find {}".format(path))
zhangyongpeng70756972023-04-12 15:31:33 +08001424
1425 if not split_args:
1426 return split_args
1427
1428 if isinstance(split_args, list):
1429 for index, arg in enumerate(split_args[:-1]):
1430 if arg == '--signing_helper':
1431 signing_helper_path = split_args[index + 1]
1432 split_args[index + 1] = ResolveBinaryPath(signing_helper_path)
1433 break
1434 elif isinstance(split_args, str):
1435 split_args = ResolveBinaryPath(split_args)
1436
1437 return split_args
1438
1439
Tao Bao765668f2019-10-04 22:03:00 -07001440def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001441 """Returns the VBMeta arguments for partition.
1442
1443 It sets up the VBMeta argument by including the partition descriptor from the
1444 given 'image', or by configuring the partition as a chained partition.
1445
1446 Args:
1447 partition: The name of the partition (e.g. "system").
1448 image: The path to the partition image.
1449 info_dict: A dict returned by common.LoadInfoDict(). Will use
1450 OPTIONS.info_dict if None has been given.
1451
1452 Returns:
1453 A list of VBMeta arguments.
1454 """
1455 if info_dict is None:
1456 info_dict = OPTIONS.info_dict
1457
1458 # Check if chain partition is used.
1459 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001460 if not key_path:
1461 return ["--include_descriptors_from_image", image]
1462
1463 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1464 # into vbmeta.img. The recovery image will be configured on an independent
1465 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1466 # See details at
1467 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001468 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001469 return []
1470
1471 # Otherwise chain the partition into vbmeta.
1472 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1473 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001474
1475
Tao Bao02a08592018-07-22 12:40:45 -07001476def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1477 """Constructs and returns the arg to build or verify a chained partition.
1478
1479 Args:
1480 partition: The partition name.
1481 info_dict: The info dict to look up the key info and rollback index
1482 location.
1483 key: The key to be used for building or verifying the partition. Defaults to
1484 the key listed in info_dict.
1485
1486 Returns:
1487 A string of form "partition:rollback_index_location:key" that can be used to
1488 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001489 """
1490 if key is None:
1491 key = info_dict["avb_" + partition + "_key_path"]
zhangyongpeng70756972023-04-12 15:31:33 +08001492 key = ResolveAVBSigningPathArgs(key)
Tao Bao1ac886e2019-06-26 11:58:22 -07001493 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001494 rollback_index_location = info_dict[
1495 "avb_" + partition + "_rollback_index_location"]
1496 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1497
1498
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001499def _HasGkiCertificationArgs():
1500 return ("gki_signing_key_path" in OPTIONS.info_dict and
1501 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001502
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001503
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001504def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001505 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001506 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001507
zhangyongpeng70756972023-04-12 15:31:33 +08001508 key_path = ResolveAVBSigningPathArgs(key_path)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001509
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001510 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001511 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001512 raise ExternalError(
1513 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001514
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001515 output_certificate = tempfile.NamedTemporaryFile()
1516 cmd = [
1517 "generate_gki_certificate",
1518 "--name", image_name,
1519 "--algorithm", algorithm,
1520 "--key", key_path,
1521 "--output", output_certificate.name,
1522 image,
1523 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001524
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001525 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1526 signature_args = signature_args.strip()
1527 if signature_args:
1528 cmd.extend(["--additional_avb_args", signature_args])
1529
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001530 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001531 args = args.strip()
1532 if args:
1533 cmd.extend(["--additional_avb_args", args])
1534
1535 RunAndCheckOutput(cmd)
1536
1537 output_certificate.seek(os.SEEK_SET, 0)
1538 data = output_certificate.read()
1539 output_certificate.close()
1540 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001541
1542
Daniel Norman276f0622019-07-26 14:13:51 -07001543def BuildVBMeta(image_path, partitions, name, needed_partitions):
1544 """Creates a VBMeta image.
1545
1546 It generates the requested VBMeta image. The requested image could be for
1547 top-level or chained VBMeta image, which is determined based on the name.
1548
1549 Args:
1550 image_path: The output path for the new VBMeta image.
1551 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001552 values. Only valid partition names are accepted, as partitions listed
1553 in common.AVB_PARTITIONS and custom partitions listed in
1554 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001555 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1556 needed_partitions: Partitions whose descriptors should be included into the
1557 generated VBMeta image.
1558
1559 Raises:
1560 AssertionError: On invalid input args.
1561 """
1562 avbtool = OPTIONS.info_dict["avb_avbtool"]
1563 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1564 AppendAVBSigningArgs(cmd, name)
1565
Hongguang Chenf23364d2020-04-27 18:36:36 -07001566 custom_partitions = OPTIONS.info_dict.get(
1567 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001568 custom_avb_partitions = ["vbmeta_" + part for part in OPTIONS.info_dict.get(
1569 "avb_custom_vbmeta_images_partition_list", "").strip().split()]
Hongguang Chenf23364d2020-04-27 18:36:36 -07001570
Daniel Norman276f0622019-07-26 14:13:51 -07001571 for partition, path in partitions.items():
1572 if partition not in needed_partitions:
1573 continue
1574 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001575 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001576 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001577 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001578 'Unknown partition: {}'.format(partition)
1579 assert os.path.exists(path), \
1580 'Failed to find {} for {}'.format(path, partition)
1581 cmd.extend(GetAvbPartitionArg(partition, path))
1582
1583 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1584 if args and args.strip():
1585 split_args = shlex.split(args)
1586 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001587 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001588 # as a path relative to source tree, which may not be available at the
1589 # same location when running this script (we have the input target_files
1590 # zip only). For such cases, we additionally scan other locations (e.g.
1591 # IMAGES/, RADIO/, etc) before bailing out.
1592 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001593 chained_image = split_args[index + 1]
1594 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001595 continue
1596 found = False
1597 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1598 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001599 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001600 if os.path.exists(alt_path):
1601 split_args[index + 1] = alt_path
1602 found = True
1603 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001604 assert found, 'Failed to find {}'.format(chained_image)
zhangyongpeng70756972023-04-12 15:31:33 +08001605
1606 split_args = ResolveAVBSigningPathArgs(split_args)
Daniel Norman276f0622019-07-26 14:13:51 -07001607 cmd.extend(split_args)
1608
1609 RunAndCheckOutput(cmd)
1610
1611
jiajia tang836f76b2021-04-02 14:48:26 +08001612def _MakeRamdisk(sourcedir, fs_config_file=None,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001613 dev_node_file=None,
jiajia tang836f76b2021-04-02 14:48:26 +08001614 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001615 ramdisk_img = tempfile.NamedTemporaryFile()
1616
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001617 cmd = ["mkbootfs"]
1618
1619 if fs_config_file and os.access(fs_config_file, os.F_OK):
1620 cmd.extend(["-f", fs_config_file])
1621
1622 if dev_node_file and os.access(dev_node_file, os.F_OK):
1623 cmd.extend(["-n", dev_node_file])
1624
1625 cmd.append(os.path.join(sourcedir, "RAMDISK"))
1626
Steve Mucklee1b10862019-07-10 10:49:37 -07001627 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001628 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001629 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001630 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001631 elif ramdisk_format == RamdiskFormat.GZ:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001632 p2 = Run(["gzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001633 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001634 raise ValueError("Only support lz4 or gzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001635
1636 p2.wait()
1637 p1.wait()
1638 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001639 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001640
1641 return ramdisk_img
1642
1643
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001644def _BuildBootableImage(image_name, sourcedir, fs_config_file,
1645 dev_node_file=None, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001646 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001647 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001648
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001649 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001650 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1651 we are building a two-step special image (i.e. building a recovery image to
1652 be loaded into /boot in two-step OTAs).
1653
1654 Return the image data, or None if sourcedir does not appear to contains files
1655 for building the requested image.
1656 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001657
Yifan Hong63c5ca12020-10-08 11:54:02 -07001658 if info_dict is None:
1659 info_dict = OPTIONS.info_dict
1660
Steve Muckle9793cf62020-04-08 18:27:00 -07001661 # "boot" or "recovery", without extension.
1662 partition_name = os.path.basename(sourcedir).lower()
1663
Yifan Hong63c5ca12020-10-08 11:54:02 -07001664 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001665 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001666 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1667 logger.info("Excluded kernel binary from recovery image.")
1668 else:
1669 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001670 elif partition_name == "init_boot":
1671 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001672 else:
1673 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001674 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001675 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001676 return None
1677
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001678 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1679
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001680 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001681 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001682
Doug Zongkereef39442009-04-02 12:14:19 -07001683 img = tempfile.NamedTemporaryFile()
1684
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001685 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001686 ramdisk_format = GetRamdiskFormat(info_dict)
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001687 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, dev_node_file,
jiajia tang836f76b2021-04-02 14:48:26 +08001688 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001689
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001690 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1691 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1692
Yifan Hong63c5ca12020-10-08 11:54:02 -07001693 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001694 if kernel_path is not None:
1695 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001696
Benoit Fradina45a8682014-07-14 21:00:43 +02001697 fn = os.path.join(sourcedir, "second")
1698 if os.access(fn, os.F_OK):
1699 cmd.append("--second")
1700 cmd.append(fn)
1701
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001702 fn = os.path.join(sourcedir, "dtb")
1703 if os.access(fn, os.F_OK):
1704 cmd.append("--dtb")
1705 cmd.append(fn)
1706
Doug Zongker171f1cd2009-06-15 22:36:37 -07001707 fn = os.path.join(sourcedir, "cmdline")
1708 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001709 cmd.append("--cmdline")
1710 cmd.append(open(fn).read().rstrip("\n"))
1711
1712 fn = os.path.join(sourcedir, "base")
1713 if os.access(fn, os.F_OK):
1714 cmd.append("--base")
1715 cmd.append(open(fn).read().rstrip("\n"))
1716
Ying Wang4de6b5b2010-08-25 14:29:34 -07001717 fn = os.path.join(sourcedir, "pagesize")
1718 if os.access(fn, os.F_OK):
1719 cmd.append("--pagesize")
1720 cmd.append(open(fn).read().rstrip("\n"))
1721
Steve Mucklef84668e2020-03-16 19:13:46 -07001722 if partition_name == "recovery":
1723 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301724 if not args:
1725 # Fall back to "mkbootimg_args" for recovery image
1726 # in case "recovery_mkbootimg_args" is not set.
1727 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001728 elif partition_name == "init_boot":
1729 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001730 else:
1731 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001732 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001733 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001734
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001735 args = info_dict.get("mkbootimg_version_args")
1736 if args and args.strip():
1737 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001738
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001739 if has_ramdisk:
1740 cmd.extend(["--ramdisk", ramdisk_img.name])
1741
Tao Baod95e9fd2015-03-29 23:07:41 -07001742 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001743 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001744 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001745 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001746 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001747 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001748
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001749 if partition_name == "recovery":
1750 if info_dict.get("include_recovery_dtbo") == "true":
1751 fn = os.path.join(sourcedir, "recovery_dtbo")
1752 cmd.extend(["--recovery_dtbo", fn])
1753 if info_dict.get("include_recovery_acpio") == "true":
1754 fn = os.path.join(sourcedir, "recovery_acpio")
1755 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001756
Tao Bao986ee862018-10-04 15:46:16 -07001757 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001758
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001759 if _HasGkiCertificationArgs():
1760 if not os.path.exists(img.name):
1761 raise ValueError("Cannot find GKI boot.img")
1762 if kernel_path is None or not os.path.exists(kernel_path):
1763 raise ValueError("Cannot find GKI kernel.img")
1764
1765 # Certify GKI images.
1766 boot_signature_bytes = b''
1767 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1768 boot_signature_bytes += _GenerateGkiCertificate(
1769 kernel_path, "generic_kernel")
1770
1771 BOOT_SIGNATURE_SIZE = 16 * 1024
1772 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1773 raise ValueError(
1774 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1775 boot_signature_bytes += (
1776 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1777 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1778
1779 with open(img.name, 'ab') as f:
1780 f.write(boot_signature_bytes)
1781
Tao Baod95e9fd2015-03-29 23:07:41 -07001782 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001783 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001784 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001785 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001786 # We have switched from the prebuilt futility binary to using the tool
1787 # (futility-host) built from the source. Override the setting in the old
1788 # TF.zip.
1789 futility = info_dict["futility"]
1790 if futility.startswith("prebuilts/"):
1791 futility = "futility-host"
1792 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001793 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001794 info_dict["vboot_key"] + ".vbprivk",
1795 info_dict["vboot_subkey"] + ".vbprivk",
1796 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001797 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001798 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001799
Tao Baof3282b42015-04-01 11:21:55 -07001800 # Clean up the temp files.
1801 img_unsigned.close()
1802 img_keyblock.close()
1803
David Zeuthen8fecb282017-12-01 16:24:01 -05001804 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001805 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001806 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001807 if partition_name == "recovery":
1808 part_size = info_dict["recovery_size"]
1809 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001810 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001811 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001812 "--partition_size", str(part_size), "--partition_name",
1813 partition_name]
1814 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001815 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001816 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001817 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1818 cmd.extend(split_args)
Tao Bao986ee862018-10-04 15:46:16 -07001819 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001820
1821 img.seek(os.SEEK_SET, 0)
1822 data = img.read()
1823
1824 if has_ramdisk:
1825 ramdisk_img.close()
1826 img.close()
1827
1828 return data
1829
1830
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001831def _SignBootableImage(image_path, prebuilt_name, partition_name,
1832 info_dict=None):
1833 """Performs AVB signing for a prebuilt boot.img.
1834
1835 Args:
1836 image_path: The full path of the image, e.g., /path/to/boot.img.
1837 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001838 boot-5.10.img, recovery.img or init_boot.img.
1839 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001840 info_dict: The information dict read from misc_info.txt.
1841 """
1842 if info_dict is None:
1843 info_dict = OPTIONS.info_dict
1844
1845 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1846 if info_dict.get("avb_enable") == "true":
1847 avbtool = info_dict["avb_avbtool"]
1848 if partition_name == "recovery":
1849 part_size = info_dict["recovery_size"]
1850 else:
1851 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1852
1853 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1854 "--partition_size", str(part_size), "--partition_name",
1855 partition_name]
1856 AppendAVBSigningArgs(cmd, partition_name)
1857 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1858 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001859 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1860 cmd.extend(split_args)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001861 RunAndCheckOutput(cmd)
1862
1863
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001864def HasRamdisk(partition_name, info_dict=None):
1865 """Returns true/false to see if a bootable image should have a ramdisk.
1866
1867 Args:
1868 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1869 info_dict: The information dict read from misc_info.txt.
1870 """
1871 if info_dict is None:
1872 info_dict = OPTIONS.info_dict
1873
1874 if partition_name != "boot":
1875 return True # init_boot.img or recovery.img has a ramdisk.
1876
1877 if info_dict.get("recovery_as_boot") == "true":
1878 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1879
Bowgo Tsai85578e02022-04-19 10:50:59 +08001880 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1881 return False # A GKI boot.img has no ramdisk since Android-13.
1882
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001883 if info_dict.get("system_root_image") == "true":
1884 # The ramdisk content is merged into the system.img, so there is NO
1885 # ramdisk in the boot.img or boot-<kernel version>.img.
1886 return False
1887
1888 if info_dict.get("init_boot") == "true":
1889 # The ramdisk is moved to the init_boot.img, so there is NO
1890 # ramdisk in the boot.img or boot-<kernel version>.img.
1891 return False
1892
1893 return True
1894
1895
Doug Zongkerd5131602012-08-02 14:46:42 -07001896def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001897 info_dict=None, two_step_image=False,
1898 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001899 """Return a File object with the desired bootable image.
1900
1901 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1902 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1903 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001904
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001905 if info_dict is None:
1906 info_dict = OPTIONS.info_dict
1907
Doug Zongker55d93282011-01-25 17:03:34 -08001908 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1909 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001910 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001911 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001912
1913 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1914 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001915 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001916 return File.FromLocalFile(name, prebuilt_path)
1917
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001918 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001919 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1920 if os.path.exists(prebuilt_path):
1921 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1922 signed_img = MakeTempFile()
1923 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001924 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1925 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001926
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001927 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001928
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001929 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001930
Doug Zongker6f1d0312014-08-22 08:07:12 -07001931 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001932 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001933 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001934 os.path.join(unpack_dir, 'META/ramdisk_node_list')
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001935 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001936 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001937 if data:
1938 return File(name, data)
1939 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001940
Doug Zongkereef39442009-04-02 12:14:19 -07001941
Lucas Wei03230252022-04-18 16:00:40 +08001942def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001943 """Build a vendor boot image from the specified sourcedir.
1944
1945 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1946 turn them into a vendor boot image.
1947
1948 Return the image data, or None if sourcedir does not appear to contains files
1949 for building the requested image.
1950 """
1951
1952 if info_dict is None:
1953 info_dict = OPTIONS.info_dict
1954
1955 img = tempfile.NamedTemporaryFile()
1956
TJ Rhoades6f488e92022-05-01 22:16:22 -07001957 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001958 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001959
1960 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1961 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1962
1963 cmd = [mkbootimg]
1964
1965 fn = os.path.join(sourcedir, "dtb")
1966 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001967 has_vendor_kernel_boot = (info_dict.get(
1968 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001969
1970 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1971 # Otherwise pack dtb into vendor_boot.
1972 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1973 cmd.append("--dtb")
1974 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001975
1976 fn = os.path.join(sourcedir, "vendor_cmdline")
1977 if os.access(fn, os.F_OK):
1978 cmd.append("--vendor_cmdline")
1979 cmd.append(open(fn).read().rstrip("\n"))
1980
1981 fn = os.path.join(sourcedir, "base")
1982 if os.access(fn, os.F_OK):
1983 cmd.append("--base")
1984 cmd.append(open(fn).read().rstrip("\n"))
1985
1986 fn = os.path.join(sourcedir, "pagesize")
1987 if os.access(fn, os.F_OK):
1988 cmd.append("--pagesize")
1989 cmd.append(open(fn).read().rstrip("\n"))
1990
1991 args = info_dict.get("mkbootimg_args")
1992 if args and args.strip():
1993 cmd.extend(shlex.split(args))
1994
1995 args = info_dict.get("mkbootimg_version_args")
1996 if args and args.strip():
1997 cmd.extend(shlex.split(args))
1998
1999 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
2000 cmd.extend(["--vendor_boot", img.name])
2001
Devin Moore50509012021-01-13 10:45:04 -08002002 fn = os.path.join(sourcedir, "vendor_bootconfig")
2003 if os.access(fn, os.F_OK):
2004 cmd.append("--vendor_bootconfig")
2005 cmd.append(fn)
2006
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002007 ramdisk_fragment_imgs = []
2008 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
2009 if os.access(fn, os.F_OK):
2010 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
2011 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002012 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2013 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002014 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04002015 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2016 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002017 # Use prebuilt image if found, else create ramdisk from supplied files.
2018 if os.access(fn, os.F_OK):
2019 ramdisk_fragment_pathname = fn
2020 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002021 ramdisk_fragment_root = os.path.join(
2022 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08002023 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
2024 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002025 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
2026 ramdisk_fragment_pathname = ramdisk_fragment_img.name
2027 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
2028
Steve Mucklee1b10862019-07-10 10:49:37 -07002029 RunAndCheckOutput(cmd)
2030
2031 # AVB: if enabled, calculate and add hash.
2032 if info_dict.get("avb_enable") == "true":
2033 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08002034 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07002035 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08002036 "--partition_size", str(part_size), "--partition_name", partition_name]
2037 AppendAVBSigningArgs(cmd, partition_name)
2038 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07002039 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08002040 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
2041 cmd.extend(split_args)
Steve Mucklee1b10862019-07-10 10:49:37 -07002042 RunAndCheckOutput(cmd)
2043
2044 img.seek(os.SEEK_SET, 0)
2045 data = img.read()
2046
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002047 for f in ramdisk_fragment_imgs:
2048 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002049 ramdisk_img.close()
2050 img.close()
2051
2052 return data
2053
2054
2055def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2056 info_dict=None):
2057 """Return a File object with the desired vendor boot image.
2058
2059 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2060 the source files in 'unpack_dir'/'tree_subdir'."""
2061
2062 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2063 if os.path.exists(prebuilt_path):
2064 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2065 return File.FromLocalFile(name, prebuilt_path)
2066
2067 logger.info("building image from target_files %s...", tree_subdir)
2068
2069 if info_dict is None:
2070 info_dict = OPTIONS.info_dict
2071
Kelvin Zhang0876c412020-06-23 15:06:58 -04002072 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002073 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2074 if data:
2075 return File(name, data)
2076 return None
2077
2078
2079def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002080 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002081 """Return a File object with the desired vendor kernel boot image.
2082
2083 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2084 the source files in 'unpack_dir'/'tree_subdir'."""
2085
2086 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2087 if os.path.exists(prebuilt_path):
2088 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2089 return File.FromLocalFile(name, prebuilt_path)
2090
2091 logger.info("building image from target_files %s...", tree_subdir)
2092
2093 if info_dict is None:
2094 info_dict = OPTIONS.info_dict
2095
2096 data = _BuildVendorBootImage(
2097 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002098 if data:
2099 return File(name, data)
2100 return None
2101
2102
Narayan Kamatha07bf042017-08-14 14:49:21 +01002103def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002104 """Gunzips the given gzip compressed file to a given output file."""
2105 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002106 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002107 shutil.copyfileobj(in_file, out_file)
2108
2109
Kelvin Zhange473ce92023-06-21 13:06:59 -07002110def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
2111 # 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
2112 # higher bits of |external_attr| are unix file permission and types
2113 unix_filetype = info.external_attr >> 16
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002114 file_perm = unix_filetype & 0o777
Kelvin Zhange473ce92023-06-21 13:06:59 -07002115
2116 def CheckMask(a, mask):
2117 return (a & mask) == mask
2118
2119 def IsSymlink(a):
2120 return CheckMask(a, stat.S_IFLNK)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002121
2122 def IsDir(a):
2123 return CheckMask(a, stat.S_IFDIR)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002124 # python3.11 zipfile implementation doesn't handle symlink correctly
2125 if not IsSymlink(unix_filetype):
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002126 target = input_zip.extract(info, dirname)
2127 # We want to ensure that the file is at least read/writable by owner and readable by all users
2128 if IsDir(unix_filetype):
2129 os.chmod(target, file_perm | 0o755)
2130 else:
2131 os.chmod(target, file_perm | 0o644)
2132 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002133 if dirname is None:
2134 dirname = os.getcwd()
2135 target = os.path.join(dirname, info.filename)
2136 os.makedirs(os.path.dirname(target), exist_ok=True)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002137 if os.path.exists(target):
2138 os.unlink(target)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002139 os.symlink(input_zip.read(info).decode(), target)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002140 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002141
2142
Tao Bao0ff15de2019-03-20 11:26:06 -07002143def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002144 """Unzips the archive to the given directory.
2145
2146 Args:
2147 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002148 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002149 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2150 archvie. Non-matching patterns will be filtered out. If there's no match
2151 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002152 """
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002153 with zipfile.ZipFile(filename, allowZip64=True, mode="r") as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002154 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002155 entries = input_zip.infolist()
2156 # b/283033491
2157 # Per https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
2158 # In zip64 mode, central directory record's header_offset field might be
2159 # set to 0xFFFFFFFF if header offset is > 2^32. In this case, the extra
2160 # fields will contain an 8 byte little endian integer at offset 20
2161 # to indicate the actual local header offset.
2162 # As of python3.11, python does not handle zip64 central directories
2163 # correctly, so we will manually do the parsing here.
Kelvin Zhang1e774242023-06-17 09:18:15 -07002164
2165 # ZIP64 central directory extra field has two required fields:
2166 # 2 bytes header ID and 2 bytes size field. Thes two require fields have
2167 # a total size of 4 bytes. Then it has three other 8 bytes field, followed
2168 # by a 4 byte disk number field. The last disk number field is not required
2169 # to be present, but if it is present, the total size of extra field will be
2170 # divisible by 8(because 2+2+4+8*n is always going to be multiple of 8)
2171 # Most extra fields are optional, but when they appear, their must appear
2172 # in the order defined by zip64 spec. Since file header offset is the 2nd
2173 # to last field in zip64 spec, it will only be at last 8 bytes or last 12-4
2174 # bytes, depending on whether disk number is present.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002175 for entry in entries:
Kelvin Zhang1e774242023-06-17 09:18:15 -07002176 if entry.header_offset == 0xFFFFFFFF:
2177 if len(entry.extra) % 8 == 0:
2178 entry.header_offset = int.from_bytes(entry.extra[-12:-4], "little")
2179 else:
2180 entry.header_offset = int.from_bytes(entry.extra[-8:], "little")
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002181 if patterns is not None:
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002182 filtered = [info for info in entries if any(
2183 [fnmatch.fnmatch(info.filename, p) for p in patterns])]
Tao Bao0ff15de2019-03-20 11:26:06 -07002184
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002185 # There isn't any matching files. Don't unzip anything.
2186 if not filtered:
2187 return
Kelvin Zhange473ce92023-06-21 13:06:59 -07002188 for info in filtered:
2189 UnzipSingleFile(input_zip, info, dirname)
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002190 else:
Kelvin Zhange473ce92023-06-21 13:06:59 -07002191 for info in entries:
2192 UnzipSingleFile(input_zip, info, dirname)
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002193
2194
Daniel Norman78554ea2021-09-14 10:29:38 -07002195def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002196 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002197
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002198 Args:
2199 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2200 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2201
Daniel Norman78554ea2021-09-14 10:29:38 -07002202 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002203 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002204
Tao Bao1c830bf2017-12-25 10:43:47 -08002205 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002206 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002207 """
Doug Zongkereef39442009-04-02 12:14:19 -07002208
Tao Bao1c830bf2017-12-25 10:43:47 -08002209 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002210 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2211 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002212 UnzipToDir(m.group(1), tmp, patterns)
2213 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002214 filename = m.group(1)
2215 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002216 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002217
Tao Baodba59ee2018-01-09 13:21:02 -08002218 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002219
2220
Yifan Hong8a66a712019-04-04 15:37:57 -07002221def GetUserImage(which, tmpdir, input_zip,
2222 info_dict=None,
2223 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002224 reset_file_map=False):
2225 """Returns an Image object suitable for passing to BlockImageDiff.
2226
2227 This function loads the specified image from the given path. If the specified
2228 image is sparse, it also performs additional processing for OTA purpose. For
2229 example, it always adds block 0 to clobbered blocks list. It also detects
2230 files that cannot be reconstructed from the block list, for whom we should
2231 avoid applying imgdiff.
2232
2233 Args:
2234 which: The partition name.
2235 tmpdir: The directory that contains the prebuilt image and block map file.
2236 input_zip: The target-files ZIP archive.
2237 info_dict: The dict to be looked up for relevant info.
2238 allow_shared_blocks: If image is sparse, whether having shared blocks is
2239 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002240 reset_file_map: If true and image is sparse, reset file map before returning
2241 the image.
2242 Returns:
2243 A Image object. If it is a sparse image and reset_file_map is False, the
2244 image will have file_map info loaded.
2245 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002246 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002247 info_dict = LoadInfoDict(input_zip)
2248
Kelvin Zhang04521282023-03-02 09:42:52 -08002249 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002250
2251 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2252 # shared blocks (i.e. some blocks will show up in multiple files' block
2253 # list). We can only allocate such shared blocks to the first "owner", and
2254 # disable imgdiff for all later occurrences.
2255 if allow_shared_blocks is None:
2256 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2257
2258 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002259 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002260 if reset_file_map:
2261 img.ResetFileMap()
2262 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002263 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002264
2265
hungweichencc9c05d2022-08-23 05:45:42 +00002266def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002267 """Returns a Image object suitable for passing to BlockImageDiff.
2268
2269 This function loads the specified non-sparse image from the given path.
2270
2271 Args:
2272 which: The partition name.
2273 tmpdir: The directory that contains the prebuilt image and block map file.
2274 Returns:
2275 A Image object.
2276 """
2277 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2278 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2279
2280 # The image and map files must have been created prior to calling
2281 # ota_from_target_files.py (since LMP).
2282 assert os.path.exists(path) and os.path.exists(mappath)
2283
hungweichencc9c05d2022-08-23 05:45:42 +00002284 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002285
Yifan Hong8a66a712019-04-04 15:37:57 -07002286
hungweichencc9c05d2022-08-23 05:45:42 +00002287def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002288 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2289
2290 This function loads the specified sparse image from the given path, and
2291 performs additional processing for OTA purpose. For example, it always adds
2292 block 0 to clobbered blocks list. It also detects files that cannot be
2293 reconstructed from the block list, for whom we should avoid applying imgdiff.
2294
2295 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002296 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002297 tmpdir: The directory that contains the prebuilt image and block map file.
2298 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002299 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002300 Returns:
2301 A SparseImage object, with file_map info loaded.
2302 """
Tao Baoc765cca2018-01-31 17:32:40 -08002303 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2304 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2305
2306 # The image and map files must have been created prior to calling
2307 # ota_from_target_files.py (since LMP).
2308 assert os.path.exists(path) and os.path.exists(mappath)
2309
2310 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2311 # it to clobbered_blocks so that it will be written to the target
2312 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2313 clobbered_blocks = "0"
2314
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002315 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002316 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002317
2318 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2319 # if they contain all zeros. We can't reconstruct such a file from its block
2320 # list. Tag such entries accordingly. (Bug: 65213616)
2321 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002322 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002323 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002324 continue
2325
Tom Cherryd14b8952018-08-09 14:26:00 -07002326 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2327 # filename listed in system.map may contain an additional leading slash
2328 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2329 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002330 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002331 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002332 arcname = entry.lstrip('/')
2333 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002334 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002335 else:
2336 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002337
2338 assert arcname in input_zip.namelist(), \
2339 "Failed to find the ZIP entry for {}".format(entry)
2340
Tao Baoc765cca2018-01-31 17:32:40 -08002341 info = input_zip.getinfo(arcname)
2342 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002343
2344 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002345 # image, check the original block list to determine its completeness. Note
2346 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002347 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002348 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002349
Tao Baoc765cca2018-01-31 17:32:40 -08002350 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2351 ranges.extra['incomplete'] = True
2352
2353 return image
2354
2355
Doug Zongkereef39442009-04-02 12:14:19 -07002356def GetKeyPasswords(keylist):
2357 """Given a list of keys, prompt the user to enter passwords for
2358 those which require them. Return a {key: password} dict. password
2359 will be None if the key has no password."""
2360
Doug Zongker8ce7c252009-05-22 13:34:54 -07002361 no_passwords = []
2362 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002363 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002364 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002365
2366 # sorted() can't compare strings to None, so convert Nones to strings
2367 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002368 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002369 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002370 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002371 continue
2372
T.R. Fullhart37e10522013-03-18 10:31:26 -07002373 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002374 "-inform", "DER", "-nocrypt"],
2375 stdin=devnull.fileno(),
2376 stdout=devnull.fileno(),
2377 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002378 p.communicate()
2379 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002380 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002381 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002382 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002383 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2384 "-inform", "DER", "-passin", "pass:"],
2385 stdin=devnull.fileno(),
2386 stdout=devnull.fileno(),
2387 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002388 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002389 if p.returncode == 0:
2390 # Encrypted key with empty string as password.
2391 key_passwords[k] = ''
2392 elif stderr.startswith('Error decrypting key'):
2393 # Definitely encrypted key.
2394 # It would have said "Error reading key" if it didn't parse correctly.
2395 need_passwords.append(k)
2396 else:
2397 # Potentially, a type of key that openssl doesn't understand.
2398 # We'll let the routines in signapk.jar handle it.
2399 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002400 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002401
T.R. Fullhart37e10522013-03-18 10:31:26 -07002402 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002403 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002404 return key_passwords
2405
2406
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002407def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002408 """Gets the minSdkVersion declared in the APK.
2409
Martin Stjernholm58472e82022-01-07 22:08:47 +00002410 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2411 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002412
2413 Args:
2414 apk_name: The APK filename.
2415
2416 Returns:
2417 The parsed SDK version string.
2418
2419 Raises:
2420 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002421 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002422 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002423 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002424 stderr=subprocess.PIPE)
2425 stdoutdata, stderrdata = proc.communicate()
2426 if proc.returncode != 0:
2427 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002428 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2429 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002430
Tao Baof47bf0f2018-03-21 23:28:51 -07002431 for line in stdoutdata.split("\n"):
2432 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002433 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2434 if m:
2435 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002436 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002437
2438
2439def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002440 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002441
Tao Baof47bf0f2018-03-21 23:28:51 -07002442 If minSdkVersion is set to a codename, it is translated to a number using the
2443 provided map.
2444
2445 Args:
2446 apk_name: The APK filename.
2447
2448 Returns:
2449 The parsed SDK version number.
2450
2451 Raises:
2452 ExternalError: On failing to get the min SDK version number.
2453 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002454 version = GetMinSdkVersion(apk_name)
2455 try:
2456 return int(version)
2457 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002458 # Not a decimal number.
2459 #
2460 # It could be either a straight codename, e.g.
2461 # UpsideDownCake
2462 #
2463 # Or a codename with API fingerprint SHA, e.g.
2464 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2465 #
2466 # Extract the codename and try and map it to a version number.
2467 split = version.split(".")
2468 codename = split[0]
2469 if codename in codename_to_api_level_map:
2470 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002471 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002472 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2473 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002474
2475
2476def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002477 codename_to_api_level_map=None, whole_file=False,
2478 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002479 """Sign the input_name zip/jar/apk, producing output_name. Use the
2480 given key and password (the latter may be None if the key does not
2481 have a password.
2482
Doug Zongker951495f2009-08-14 12:44:19 -07002483 If whole_file is true, use the "-w" option to SignApk to embed a
2484 signature that covers the whole file in the archive comment of the
2485 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002486
2487 min_api_level is the API Level (int) of the oldest platform this file may end
2488 up on. If not specified for an APK, the API Level is obtained by interpreting
2489 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2490
2491 codename_to_api_level_map is needed to translate the codename which may be
2492 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002493
2494 Caller may optionally specify extra args to be passed to SignApk, which
2495 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002496 """
Tao Bao76def242017-11-21 09:25:31 -08002497 if codename_to_api_level_map is None:
2498 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002499 if extra_signapk_args is None:
2500 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002501
Alex Klyubin9667b182015-12-10 13:38:50 -08002502 java_library_path = os.path.join(
2503 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2504
Tao Baoe95540e2016-11-08 12:08:53 -08002505 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2506 ["-Djava.library.path=" + java_library_path,
2507 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002508 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002509 if whole_file:
2510 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002511
2512 min_sdk_version = min_api_level
2513 if min_sdk_version is None:
2514 if not whole_file:
2515 min_sdk_version = GetMinSdkVersionInt(
2516 input_name, codename_to_api_level_map)
2517 if min_sdk_version is not None:
2518 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2519
T.R. Fullhart37e10522013-03-18 10:31:26 -07002520 cmd.extend([key + OPTIONS.public_key_suffix,
2521 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002522 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002523
Tao Bao73dd4f42018-10-04 16:25:33 -07002524 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002525 if password is not None:
2526 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002527 stdoutdata, _ = proc.communicate(password)
2528 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002529 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002530 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002531 proc.returncode, stdoutdata))
2532
Doug Zongkereef39442009-04-02 12:14:19 -07002533
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002534def SignSePolicy(sepolicy, key, password):
2535 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2536 an RSA .sig signature files.
2537 """
2538
2539 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002540 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002541 return False
2542
2543 java_library_path = os.path.join(
2544 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2545
2546 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002547 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002548 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002549 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002550
2551 cmd.extend([key + OPTIONS.public_key_suffix,
2552 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002553 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002554
2555 proc = Run(cmd, stdin=subprocess.PIPE)
2556 if password is not None:
2557 password += "\n"
2558 stdoutdata, _ = proc.communicate(password)
2559 if proc.returncode != 0:
2560 raise ExternalError(
2561 "Failed to run sign sepolicy: return code {}:\n{}".format(
2562 proc.returncode, stdoutdata))
2563 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002564
Kelvin Zhangf294c872022-10-06 14:21:36 -07002565
Doug Zongker37974732010-09-16 17:44:38 -07002566def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002567 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002568
Tao Bao9dd909e2017-11-14 11:27:32 -08002569 For non-AVB images, raise exception if the data is too big. Print a warning
2570 if the data is nearing the maximum size.
2571
2572 For AVB images, the actual image size should be identical to the limit.
2573
2574 Args:
2575 data: A string that contains all the data for the partition.
2576 target: The partition name. The ".img" suffix is optional.
2577 info_dict: The dict to be looked up for relevant info.
2578 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002579 if target.endswith(".img"):
2580 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002581 mount_point = "/" + target
2582
Ying Wangf8824af2014-06-03 14:07:27 -07002583 fs_type = None
2584 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002585 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002586 if mount_point == "/userdata":
2587 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002588 p = info_dict["fstab"][mount_point]
2589 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002590 device = p.device
2591 if "/" in device:
2592 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002593 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002594 if not fs_type or not limit:
2595 return
Doug Zongkereef39442009-04-02 12:14:19 -07002596
Andrew Boie0f9aec82012-02-14 09:32:52 -08002597 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002598 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2599 # path.
2600 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2601 if size != limit:
2602 raise ExternalError(
2603 "Mismatching image size for %s: expected %d actual %d" % (
2604 target, limit, size))
2605 else:
2606 pct = float(size) * 100.0 / limit
2607 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2608 if pct >= 99.0:
2609 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002610
2611 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002612 logger.warning("\n WARNING: %s\n", msg)
2613 else:
2614 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002615
2616
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002617def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002618 """Parses the APK certs info from a given target-files zip.
2619
2620 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2621 tuple with the following elements: (1) a dictionary that maps packages to
2622 certs (based on the "certificate" and "private_key" attributes in the file;
2623 (2) a string representing the extension of compressed APKs in the target files
2624 (e.g ".gz", ".bro").
2625
2626 Args:
2627 tf_zip: The input target_files ZipFile (already open).
2628
2629 Returns:
2630 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2631 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2632 no compressed APKs.
2633 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002634 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002635 compressed_extension = None
2636
Tao Bao0f990332017-09-08 19:02:54 -07002637 # META/apkcerts.txt contains the info for _all_ the packages known at build
2638 # time. Filter out the ones that are not installed.
2639 installed_files = set()
2640 for name in tf_zip.namelist():
2641 basename = os.path.basename(name)
2642 if basename:
2643 installed_files.add(basename)
2644
Tao Baoda30cfa2017-12-01 16:19:46 -08002645 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002646 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002647 if not line:
2648 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002649 m = re.match(
2650 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002651 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2652 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002653 line)
2654 if not m:
2655 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002656
Tao Bao818ddf52018-01-05 11:17:34 -08002657 matches = m.groupdict()
2658 cert = matches["CERT"]
2659 privkey = matches["PRIVKEY"]
2660 name = matches["NAME"]
2661 this_compressed_extension = matches["COMPRESSED"]
2662
2663 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2664 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2665 if cert in SPECIAL_CERT_STRINGS and not privkey:
2666 certmap[name] = cert
2667 elif (cert.endswith(OPTIONS.public_key_suffix) and
2668 privkey.endswith(OPTIONS.private_key_suffix) and
2669 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2670 certmap[name] = cert[:-public_key_suffix_len]
2671 else:
2672 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2673
2674 if not this_compressed_extension:
2675 continue
2676
2677 # Only count the installed files.
2678 filename = name + '.' + this_compressed_extension
2679 if filename not in installed_files:
2680 continue
2681
2682 # Make sure that all the values in the compression map have the same
2683 # extension. We don't support multiple compression methods in the same
2684 # system image.
2685 if compressed_extension:
2686 if this_compressed_extension != compressed_extension:
2687 raise ValueError(
2688 "Multiple compressed extensions: {} vs {}".format(
2689 compressed_extension, this_compressed_extension))
2690 else:
2691 compressed_extension = this_compressed_extension
2692
2693 return (certmap,
2694 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002695
2696
Doug Zongkereef39442009-04-02 12:14:19 -07002697COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002698Global options
2699
2700 -p (--path) <dir>
2701 Prepend <dir>/bin to the list of places to search for binaries run by this
2702 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002703
Doug Zongker05d3dea2009-06-22 11:32:31 -07002704 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002705 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002706
Tao Bao30df8b42018-04-23 15:32:53 -07002707 -x (--extra) <key=value>
2708 Add a key/value pair to the 'extras' dict, which device-specific extension
2709 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002710
Doug Zongkereef39442009-04-02 12:14:19 -07002711 -v (--verbose)
2712 Show command lines being executed.
2713
2714 -h (--help)
2715 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002716
2717 --logfile <file>
2718 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002719"""
2720
Kelvin Zhang0876c412020-06-23 15:06:58 -04002721
Doug Zongkereef39442009-04-02 12:14:19 -07002722def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002723 print(docstring.rstrip("\n"))
2724 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002725
2726
2727def ParseOptions(argv,
2728 docstring,
2729 extra_opts="", extra_long_opts=(),
2730 extra_option_handler=None):
2731 """Parse the options in argv and return any arguments that aren't
2732 flags. docstring is the calling module's docstring, to be displayed
2733 for errors and -h. extra_opts and extra_long_opts are for flags
2734 defined by the caller, which are processed by passing them to
2735 extra_option_handler."""
2736
2737 try:
2738 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002739 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002740 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002741 "signapk_shared_library_path=", "extra_signapk_args=",
2742 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002743 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002744 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2745 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002746 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002747 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002748 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002749 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002750 sys.exit(2)
2751
Doug Zongkereef39442009-04-02 12:14:19 -07002752 for o, a in opts:
2753 if o in ("-h", "--help"):
2754 Usage(docstring)
2755 sys.exit()
2756 elif o in ("-v", "--verbose"):
2757 OPTIONS.verbose = True
2758 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002759 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002760 elif o in ("--signapk_path",):
2761 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002762 elif o in ("--signapk_shared_library_path",):
2763 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002764 elif o in ("--extra_signapk_args",):
2765 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002766 elif o in ("--sign_sepolicy_path",):
2767 OPTIONS.sign_sepolicy_path = a
2768 elif o in ("--extra_sign_sepolicy_args",):
2769 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002770 elif o in ("--aapt2_path",):
2771 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002772 elif o in ("--java_path",):
2773 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002774 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002775 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002776 elif o in ("--android_jar_path",):
2777 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002778 elif o in ("--public_key_suffix",):
2779 OPTIONS.public_key_suffix = a
2780 elif o in ("--private_key_suffix",):
2781 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002782 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002783 raise ValueError(
2784 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002785 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002786 raise ValueError(
2787 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002788 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002789 raise ValueError(
2790 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002791 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002792 raise ValueError(
2793 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002794 elif o in ("-s", "--device_specific"):
2795 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002796 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002797 key, value = a.split("=", 1)
2798 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002799 elif o in ("--logfile",):
2800 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002801 else:
2802 if extra_option_handler is None or not extra_option_handler(o, a):
2803 assert False, "unknown option \"%s\"" % (o,)
2804
Doug Zongker85448772014-09-09 14:59:20 -07002805 if OPTIONS.search_path:
2806 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2807 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002808
2809 return args
2810
2811
Tao Bao4c851b12016-09-19 13:54:38 -07002812def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002813 """Make a temp file and add it to the list of things to be deleted
2814 when Cleanup() is called. Return the filename."""
2815 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2816 os.close(fd)
2817 OPTIONS.tempfiles.append(fn)
2818 return fn
2819
2820
Tao Bao1c830bf2017-12-25 10:43:47 -08002821def MakeTempDir(prefix='tmp', suffix=''):
2822 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2823
2824 Returns:
2825 The absolute pathname of the new directory.
2826 """
2827 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2828 OPTIONS.tempfiles.append(dir_name)
2829 return dir_name
2830
2831
Doug Zongkereef39442009-04-02 12:14:19 -07002832def Cleanup():
2833 for i in OPTIONS.tempfiles:
Kelvin Zhang22680912023-05-19 13:12:59 -07002834 if not os.path.exists(i):
2835 continue
Doug Zongkereef39442009-04-02 12:14:19 -07002836 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002837 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002838 else:
2839 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002840 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002841
2842
2843class PasswordManager(object):
2844 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002845 self.editor = os.getenv("EDITOR")
2846 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002847
2848 def GetPasswords(self, items):
2849 """Get passwords corresponding to each string in 'items',
2850 returning a dict. (The dict may have keys in addition to the
2851 values in 'items'.)
2852
2853 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2854 user edit that file to add more needed passwords. If no editor is
2855 available, or $ANDROID_PW_FILE isn't define, prompts the user
2856 interactively in the ordinary way.
2857 """
2858
2859 current = self.ReadFile()
2860
2861 first = True
2862 while True:
2863 missing = []
2864 for i in items:
2865 if i not in current or not current[i]:
2866 missing.append(i)
2867 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002868 if not missing:
2869 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002870
2871 for i in missing:
2872 current[i] = ""
2873
2874 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002875 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002876 if sys.version_info[0] >= 3:
2877 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002878 answer = raw_input("try to edit again? [y]> ").strip()
2879 if answer and answer[0] not in 'yY':
2880 raise RuntimeError("key passwords unavailable")
2881 first = False
2882
2883 current = self.UpdateAndReadFile(current)
2884
Kelvin Zhang0876c412020-06-23 15:06:58 -04002885 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002886 """Prompt the user to enter a value (password) for each key in
2887 'current' whose value is fales. Returns a new dict with all the
2888 values.
2889 """
2890 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002891 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002892 if v:
2893 result[k] = v
2894 else:
2895 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002896 result[k] = getpass.getpass(
2897 "Enter password for %s key> " % k).strip()
2898 if result[k]:
2899 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002900 return result
2901
2902 def UpdateAndReadFile(self, current):
2903 if not self.editor or not self.pwfile:
2904 return self.PromptResult(current)
2905
2906 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002907 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002908 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2909 f.write("# (Additional spaces are harmless.)\n\n")
2910
2911 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002912 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002913 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002914 f.write("[[[ %s ]]] %s\n" % (v, k))
2915 if not v and first_line is None:
2916 # position cursor on first line with no password.
2917 first_line = i + 4
2918 f.close()
2919
Tao Bao986ee862018-10-04 15:46:16 -07002920 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002921
2922 return self.ReadFile()
2923
2924 def ReadFile(self):
2925 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002926 if self.pwfile is None:
2927 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002928 try:
2929 f = open(self.pwfile, "r")
2930 for line in f:
2931 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002932 if not line or line[0] == '#':
2933 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002934 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2935 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002936 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002937 else:
2938 result[m.group(2)] = m.group(1)
2939 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002940 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002941 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002942 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002943 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002944
2945
Dan Albert8e0178d2015-01-27 15:53:15 -08002946def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2947 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002948
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002949 # http://b/18015246
2950 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2951 # for files larger than 2GiB. We can work around this by adjusting their
2952 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2953 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2954 # it isn't clear to me exactly what circumstances cause this).
2955 # `zipfile.write()` must be used directly to work around this.
2956 #
2957 # This mess can be avoided if we port to python3.
2958 saved_zip64_limit = zipfile.ZIP64_LIMIT
2959 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2960
Dan Albert8e0178d2015-01-27 15:53:15 -08002961 if compress_type is None:
2962 compress_type = zip_file.compression
2963 if arcname is None:
2964 arcname = filename
2965
2966 saved_stat = os.stat(filename)
2967
2968 try:
2969 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2970 # file to be zipped and reset it when we're done.
2971 os.chmod(filename, perms)
2972
2973 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002974 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2975 # intentional. zip stores datetimes in local time without a time zone
2976 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2977 # in the zip archive.
2978 local_epoch = datetime.datetime.fromtimestamp(0)
2979 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002980 os.utime(filename, (timestamp, timestamp))
2981
2982 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2983 finally:
2984 os.chmod(filename, saved_stat.st_mode)
2985 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002986 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08002987
2988
Tao Bao58c1b962015-05-20 09:32:18 -07002989def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002990 compress_type=None):
2991 """Wrap zipfile.writestr() function to work around the zip64 limit.
2992
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002993 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07002994 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2995 when calling crc32(bytes).
2996
2997 But it still works fine to write a shorter string into a large zip file.
2998 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2999 when we know the string won't be too long.
3000 """
3001
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003002 saved_zip64_limit = zipfile.ZIP64_LIMIT
3003 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3004
Tao Baof3282b42015-04-01 11:21:55 -07003005 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
3006 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07003007 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07003008 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07003009 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08003010 else:
Tao Baof3282b42015-04-01 11:21:55 -07003011 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07003012 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
3013 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
3014 # such a case (since
3015 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
3016 # which seems to make more sense. Otherwise the entry will have 0o000 as the
3017 # permission bits. We follow the logic in Python 3 to get consistent
3018 # behavior between using the two versions.
3019 if not zinfo.external_attr:
3020 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07003021
3022 # If compress_type is given, it overrides the value in zinfo.
3023 if compress_type is not None:
3024 zinfo.compress_type = compress_type
3025
Tao Bao58c1b962015-05-20 09:32:18 -07003026 # If perms is given, it has a priority.
3027 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07003028 # If perms doesn't set the file type, mark it as a regular file.
3029 if perms & 0o770000 == 0:
3030 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07003031 zinfo.external_attr = perms << 16
3032
Tao Baof3282b42015-04-01 11:21:55 -07003033 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07003034 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
3035
Dan Albert8b72aef2015-03-23 19:13:21 -07003036 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003037 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07003038
3039
Kelvin Zhang1caead02022-09-23 10:06:03 -07003040def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08003041 """Deletes entries from a ZIP file.
3042
Tao Bao89d7ab22017-12-14 17:05:33 -08003043 Args:
3044 zip_filename: The name of the ZIP file.
3045 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08003046 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07003047 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08003048 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08003049 # If list is empty, nothing to do
3050 if not entries:
3051 return
Wei Li8895f9e2022-10-10 17:13:17 -07003052
3053 with zipfile.ZipFile(zip_filename, 'r') as zin:
3054 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
3055 raise ExternalError(
3056 "Failed to delete zip entries, name not matched: %s" % entries)
3057
3058 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
3059 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08003060 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
3061 for entry in entries:
3062 cmd.append("-x")
3063 cmd.append(entry)
3064 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07003065
Wei Li8895f9e2022-10-10 17:13:17 -07003066 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08003067
3068
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003069def ZipClose(zip_file):
3070 # http://b/18015246
3071 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
3072 # central directory.
3073 saved_zip64_limit = zipfile.ZIP64_LIMIT
3074 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3075
3076 zip_file.close()
3077
3078 zipfile.ZIP64_LIMIT = saved_zip64_limit
3079
3080
Doug Zongker05d3dea2009-06-22 11:32:31 -07003081class DeviceSpecificParams(object):
3082 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04003083
Doug Zongker05d3dea2009-06-22 11:32:31 -07003084 def __init__(self, **kwargs):
3085 """Keyword arguments to the constructor become attributes of this
3086 object, which is passed to all functions in the device-specific
3087 module."""
Tao Bao38884282019-07-10 22:20:56 -07003088 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07003089 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08003090 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07003091
3092 if self.module is None:
3093 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07003094 if not path:
3095 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003096 try:
3097 if os.path.isdir(path):
3098 info = imp.find_module("releasetools", [path])
3099 else:
3100 d, f = os.path.split(path)
3101 b, x = os.path.splitext(f)
3102 if x == ".py":
3103 f = b
3104 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07003105 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003106 self.module = imp.load_module("device_specific", *info)
3107 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07003108 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003109
3110 def _DoCall(self, function_name, *args, **kwargs):
3111 """Call the named function in the device-specific module, passing
3112 the given args and kwargs. The first argument to the call will be
3113 the DeviceSpecific object itself. If there is no module, or the
3114 module does not define the function, return the value of the
3115 'default' kwarg (which itself defaults to None)."""
3116 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08003117 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003118 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
3119
3120 def FullOTA_Assertions(self):
3121 """Called after emitting the block of assertions at the top of a
3122 full OTA package. Implementations can add whatever additional
3123 assertions they like."""
3124 return self._DoCall("FullOTA_Assertions")
3125
Doug Zongkere5ff5902012-01-17 10:55:37 -08003126 def FullOTA_InstallBegin(self):
3127 """Called at the start of full OTA installation."""
3128 return self._DoCall("FullOTA_InstallBegin")
3129
Yifan Hong10c530d2018-12-27 17:34:18 -08003130 def FullOTA_GetBlockDifferences(self):
3131 """Called during full OTA installation and verification.
3132 Implementation should return a list of BlockDifference objects describing
3133 the update on each additional partitions.
3134 """
3135 return self._DoCall("FullOTA_GetBlockDifferences")
3136
Doug Zongker05d3dea2009-06-22 11:32:31 -07003137 def FullOTA_InstallEnd(self):
3138 """Called at the end of full OTA installation; typically this is
3139 used to install the image for the device's baseband processor."""
3140 return self._DoCall("FullOTA_InstallEnd")
3141
3142 def IncrementalOTA_Assertions(self):
3143 """Called after emitting the block of assertions at the top of an
3144 incremental OTA package. Implementations can add whatever
3145 additional assertions they like."""
3146 return self._DoCall("IncrementalOTA_Assertions")
3147
Doug Zongkere5ff5902012-01-17 10:55:37 -08003148 def IncrementalOTA_VerifyBegin(self):
3149 """Called at the start of the verification phase of incremental
3150 OTA installation; additional checks can be placed here to abort
3151 the script before any changes are made."""
3152 return self._DoCall("IncrementalOTA_VerifyBegin")
3153
Doug Zongker05d3dea2009-06-22 11:32:31 -07003154 def IncrementalOTA_VerifyEnd(self):
3155 """Called at the end of the verification phase of incremental OTA
3156 installation; additional checks can be placed here to abort the
3157 script before any changes are made."""
3158 return self._DoCall("IncrementalOTA_VerifyEnd")
3159
Doug Zongkere5ff5902012-01-17 10:55:37 -08003160 def IncrementalOTA_InstallBegin(self):
3161 """Called at the start of incremental OTA installation (after
3162 verification is complete)."""
3163 return self._DoCall("IncrementalOTA_InstallBegin")
3164
Yifan Hong10c530d2018-12-27 17:34:18 -08003165 def IncrementalOTA_GetBlockDifferences(self):
3166 """Called during incremental OTA installation and verification.
3167 Implementation should return a list of BlockDifference objects describing
3168 the update on each additional partitions.
3169 """
3170 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3171
Doug Zongker05d3dea2009-06-22 11:32:31 -07003172 def IncrementalOTA_InstallEnd(self):
3173 """Called at the end of incremental OTA installation; typically
3174 this is used to install the image for the device's baseband
3175 processor."""
3176 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003177
Tao Bao9bc6bb22015-11-09 16:58:28 -08003178 def VerifyOTA_Assertions(self):
3179 return self._DoCall("VerifyOTA_Assertions")
3180
Tao Bao76def242017-11-21 09:25:31 -08003181
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003182class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003183 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003184 self.name = name
3185 self.data = data
3186 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003187 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003188 self.sha1 = sha1(data).hexdigest()
3189
3190 @classmethod
3191 def FromLocalFile(cls, name, diskname):
3192 f = open(diskname, "rb")
3193 data = f.read()
3194 f.close()
3195 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003196
3197 def WriteToTemp(self):
3198 t = tempfile.NamedTemporaryFile()
3199 t.write(self.data)
3200 t.flush()
3201 return t
3202
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003203 def WriteToDir(self, d):
3204 with open(os.path.join(d, self.name), "wb") as fp:
3205 fp.write(self.data)
3206
Geremy Condra36bd3652014-02-06 19:45:10 -08003207 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003208 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003209
Tao Bao76def242017-11-21 09:25:31 -08003210
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003211DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003212 ".gz": "imgdiff",
3213 ".zip": ["imgdiff", "-z"],
3214 ".jar": ["imgdiff", "-z"],
3215 ".apk": ["imgdiff", "-z"],
3216 ".img": "imgdiff",
3217}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003218
Tao Bao76def242017-11-21 09:25:31 -08003219
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003220class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003221 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003222 self.tf = tf
3223 self.sf = sf
3224 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003225 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003226
3227 def ComputePatch(self):
3228 """Compute the patch (as a string of data) needed to turn sf into
3229 tf. Returns the same tuple as GetPatch()."""
3230
3231 tf = self.tf
3232 sf = self.sf
3233
Doug Zongker24cd2802012-08-14 16:36:15 -07003234 if self.diff_program:
3235 diff_program = self.diff_program
3236 else:
3237 ext = os.path.splitext(tf.name)[1]
3238 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003239
3240 ttemp = tf.WriteToTemp()
3241 stemp = sf.WriteToTemp()
3242
3243 ext = os.path.splitext(tf.name)[1]
3244
3245 try:
3246 ptemp = tempfile.NamedTemporaryFile()
3247 if isinstance(diff_program, list):
3248 cmd = copy.copy(diff_program)
3249 else:
3250 cmd = [diff_program]
3251 cmd.append(stemp.name)
3252 cmd.append(ttemp.name)
3253 cmd.append(ptemp.name)
3254 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003255 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003256
Doug Zongkerf8340082014-08-05 10:39:37 -07003257 def run():
3258 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003259 if e:
3260 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003261 th = threading.Thread(target=run)
3262 th.start()
3263 th.join(timeout=300) # 5 mins
3264 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003265 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003266 p.terminate()
3267 th.join(5)
3268 if th.is_alive():
3269 p.kill()
3270 th.join()
3271
Tianjie Xua2a9f992018-01-05 15:15:54 -08003272 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003273 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003274 self.patch = None
3275 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003276 diff = ptemp.read()
3277 finally:
3278 ptemp.close()
3279 stemp.close()
3280 ttemp.close()
3281
3282 self.patch = diff
3283 return self.tf, self.sf, self.patch
3284
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003285 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003286 """Returns a tuple of (target_file, source_file, patch_data).
3287
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003288 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003289 computing the patch failed.
3290 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003291 return self.tf, self.sf, self.patch
3292
3293
3294def ComputeDifferences(diffs):
3295 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003296 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003297
3298 # Do the largest files first, to try and reduce the long-pole effect.
3299 by_size = [(i.tf.size, i) for i in diffs]
3300 by_size.sort(reverse=True)
3301 by_size = [i[1] for i in by_size]
3302
3303 lock = threading.Lock()
3304 diff_iter = iter(by_size) # accessed under lock
3305
3306 def worker():
3307 try:
3308 lock.acquire()
3309 for d in diff_iter:
3310 lock.release()
3311 start = time.time()
3312 d.ComputePatch()
3313 dur = time.time() - start
3314 lock.acquire()
3315
3316 tf, sf, patch = d.GetPatch()
3317 if sf.name == tf.name:
3318 name = tf.name
3319 else:
3320 name = "%s (%s)" % (tf.name, sf.name)
3321 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003322 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003323 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003324 logger.info(
3325 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3326 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003327 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003328 except Exception:
3329 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003330 raise
3331
3332 # start worker threads; wait for them all to finish.
3333 threads = [threading.Thread(target=worker)
3334 for i in range(OPTIONS.worker_threads)]
3335 for th in threads:
3336 th.start()
3337 while threads:
3338 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003339
3340
Dan Albert8b72aef2015-03-23 19:13:21 -07003341class BlockDifference(object):
3342 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003343 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003344 self.tgt = tgt
3345 self.src = src
3346 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003347 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003348 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003349
Tao Baodd2a5892015-03-12 12:32:37 -07003350 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003351 version = max(
3352 int(i) for i in
3353 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003354 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003355 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003356
Tianjie Xu41976c72019-07-03 13:57:01 -07003357 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3358 version=self.version,
3359 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003360 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003361 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003362 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003363 self.touched_src_ranges = b.touched_src_ranges
3364 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003365
Yifan Hong10c530d2018-12-27 17:34:18 -08003366 # On devices with dynamic partitions, for new partitions,
3367 # src is None but OPTIONS.source_info_dict is not.
3368 if OPTIONS.source_info_dict is None:
3369 is_dynamic_build = OPTIONS.info_dict.get(
3370 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003371 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003372 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003373 is_dynamic_build = OPTIONS.source_info_dict.get(
3374 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003375 is_dynamic_source = partition in shlex.split(
3376 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003377
Yifan Hongbb2658d2019-01-25 12:30:58 -08003378 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003379 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3380
Yifan Hongbb2658d2019-01-25 12:30:58 -08003381 # For dynamic partitions builds, check partition list in both source
3382 # and target build because new partitions may be added, and existing
3383 # partitions may be removed.
3384 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3385
Yifan Hong10c530d2018-12-27 17:34:18 -08003386 if is_dynamic:
3387 self.device = 'map_partition("%s")' % partition
3388 else:
3389 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003390 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3391 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003392 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003393 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3394 OPTIONS.source_info_dict)
3395 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003396
Tao Baod8d14be2016-02-04 14:26:02 -08003397 @property
3398 def required_cache(self):
3399 return self._required_cache
3400
Tao Bao76def242017-11-21 09:25:31 -08003401 def WriteScript(self, script, output_zip, progress=None,
3402 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003403 if not self.src:
3404 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003405 script.Print("Patching %s image unconditionally..." % (self.partition,))
3406 else:
3407 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003408
Dan Albert8b72aef2015-03-23 19:13:21 -07003409 if progress:
3410 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003411 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003412
3413 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003414 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003415
Tao Bao9bc6bb22015-11-09 16:58:28 -08003416 def WriteStrictVerifyScript(self, script):
3417 """Verify all the blocks in the care_map, including clobbered blocks.
3418
3419 This differs from the WriteVerifyScript() function: a) it prints different
3420 error messages; b) it doesn't allow half-way updated images to pass the
3421 verification."""
3422
3423 partition = self.partition
3424 script.Print("Verifying %s..." % (partition,))
3425 ranges = self.tgt.care_map
3426 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003427 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003428 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3429 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003430 self.device, ranges_str,
3431 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003432 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003433 script.AppendExtra("")
3434
Tao Baod522bdc2016-04-12 15:53:16 -07003435 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003436 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003437
3438 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003439 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003440 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003441
3442 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003443 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003444 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003445 ranges = self.touched_src_ranges
3446 expected_sha1 = self.touched_src_sha1
3447 else:
3448 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3449 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003450
3451 # No blocks to be checked, skipping.
3452 if not ranges:
3453 return
3454
Tao Bao5ece99d2015-05-12 11:42:31 -07003455 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003456 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003457 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003458 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3459 '"%s.patch.dat")) then' % (
3460 self.device, ranges_str, expected_sha1,
3461 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003462 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003463 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003464
Tianjie Xufc3422a2015-12-15 11:53:59 -08003465 if self.version >= 4:
3466
3467 # Bug: 21124327
3468 # When generating incrementals for the system and vendor partitions in
3469 # version 4 or newer, explicitly check the first block (which contains
3470 # the superblock) of the partition to see if it's what we expect. If
3471 # this check fails, give an explicit log message about the partition
3472 # having been remounted R/W (the most likely explanation).
3473 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003474 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003475
3476 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003477 if partition == "system":
3478 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3479 else:
3480 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003481 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003482 'ifelse (block_image_recover({device}, "{ranges}") && '
3483 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003484 'package_extract_file("{partition}.transfer.list"), '
3485 '"{partition}.new.dat", "{partition}.patch.dat"), '
3486 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003487 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003488 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003489 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003490
Tao Baodd2a5892015-03-12 12:32:37 -07003491 # Abort the OTA update. Note that the incremental OTA cannot be applied
3492 # even if it may match the checksum of the target partition.
3493 # a) If version < 3, operations like move and erase will make changes
3494 # unconditionally and damage the partition.
3495 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003496 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003497 if partition == "system":
3498 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3499 else:
3500 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3501 script.AppendExtra((
3502 'abort("E%d: %s partition has unexpected contents");\n'
3503 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003504
Yifan Hong10c530d2018-12-27 17:34:18 -08003505 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003506 partition = self.partition
3507 script.Print('Verifying the updated %s image...' % (partition,))
3508 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3509 ranges = self.tgt.care_map
3510 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003511 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003512 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003513 self.device, ranges_str,
3514 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003515
3516 # Bug: 20881595
3517 # Verify that extended blocks are really zeroed out.
3518 if self.tgt.extended:
3519 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003520 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003521 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003522 self.device, ranges_str,
3523 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003524 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003525 if partition == "system":
3526 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3527 else:
3528 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003529 script.AppendExtra(
3530 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003531 ' abort("E%d: %s partition has unexpected non-zero contents after '
3532 'OTA update");\n'
3533 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003534 else:
3535 script.Print('Verified the updated %s image.' % (partition,))
3536
Tianjie Xu209db462016-05-24 17:34:52 -07003537 if partition == "system":
3538 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3539 else:
3540 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3541
Tao Bao5fcaaef2015-06-01 13:40:49 -07003542 script.AppendExtra(
3543 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003544 ' abort("E%d: %s partition has unexpected contents after OTA '
3545 'update");\n'
3546 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003547
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003548 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003549 ZipWrite(output_zip,
3550 '{}.transfer.list'.format(self.path),
3551 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003552
Tao Bao76def242017-11-21 09:25:31 -08003553 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3554 # its size. Quailty 9 almost triples the compression time but doesn't
3555 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003556 # zip | brotli(quality 6) | brotli(quality 9)
3557 # compressed_size: 942M | 869M (~8% reduced) | 854M
3558 # compression_time: 75s | 265s | 719s
3559 # decompression_time: 15s | 25s | 25s
3560
3561 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003562 brotli_cmd = ['brotli', '--quality=6',
3563 '--output={}.new.dat.br'.format(self.path),
3564 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003565 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003566 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003567
3568 new_data_name = '{}.new.dat.br'.format(self.partition)
3569 ZipWrite(output_zip,
3570 '{}.new.dat.br'.format(self.path),
3571 new_data_name,
3572 compress_type=zipfile.ZIP_STORED)
3573 else:
3574 new_data_name = '{}.new.dat'.format(self.partition)
3575 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3576
Dan Albert8e0178d2015-01-27 15:53:15 -08003577 ZipWrite(output_zip,
3578 '{}.patch.dat'.format(self.path),
3579 '{}.patch.dat'.format(self.partition),
3580 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003581
Tianjie Xu209db462016-05-24 17:34:52 -07003582 if self.partition == "system":
3583 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3584 else:
3585 code = ErrorCode.VENDOR_UPDATE_FAILURE
3586
Yifan Hong10c530d2018-12-27 17:34:18 -08003587 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003588 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003589 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003590 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003591 device=self.device, partition=self.partition,
3592 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003593 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003594
Kelvin Zhang0876c412020-06-23 15:06:58 -04003595 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003596 data = source.ReadRangeSet(ranges)
3597 ctx = sha1()
3598
3599 for p in data:
3600 ctx.update(p)
3601
3602 return ctx.hexdigest()
3603
Kelvin Zhang0876c412020-06-23 15:06:58 -04003604 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003605 """Return the hash value for all zero blocks."""
3606 zero_block = '\x00' * 4096
3607 ctx = sha1()
3608 for _ in range(num_blocks):
3609 ctx.update(zero_block)
3610
3611 return ctx.hexdigest()
3612
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003613
Tianjie Xu41976c72019-07-03 13:57:01 -07003614# Expose these two classes to support vendor-specific scripts
3615DataImage = images.DataImage
3616EmptyImage = images.EmptyImage
3617
Tao Bao76def242017-11-21 09:25:31 -08003618
Doug Zongker96a57e72010-09-26 14:57:41 -07003619# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003620PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003621 "ext4": "EMMC",
3622 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003623 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003624 "squashfs": "EMMC",
3625 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003626}
Doug Zongker96a57e72010-09-26 14:57:41 -07003627
Kelvin Zhang0876c412020-06-23 15:06:58 -04003628
Yifan Hongbdb32012020-05-07 12:38:53 -07003629def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3630 """
3631 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3632 backwards compatibility. It aborts if the fstab entry has slotselect option
3633 (unless check_no_slot is explicitly set to False).
3634 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003635 fstab = info["fstab"]
3636 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003637 if check_no_slot:
3638 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003639 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003640 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3641 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003642 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003643
3644
Yifan Hongbdb32012020-05-07 12:38:53 -07003645def GetTypeAndDeviceExpr(mount_point, info):
3646 """
3647 Return the filesystem of the partition, and an edify expression that evaluates
3648 to the device at runtime.
3649 """
3650 fstab = info["fstab"]
3651 if fstab:
3652 p = fstab[mount_point]
3653 device_expr = '"%s"' % fstab[mount_point].device
3654 if p.slotselect:
3655 device_expr = 'add_slot_suffix(%s)' % device_expr
3656 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003657 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003658
3659
3660def GetEntryForDevice(fstab, device):
3661 """
3662 Returns:
3663 The first entry in fstab whose device is the given value.
3664 """
3665 if not fstab:
3666 return None
3667 for mount_point in fstab:
3668 if fstab[mount_point].device == device:
3669 return fstab[mount_point]
3670 return None
3671
Kelvin Zhang0876c412020-06-23 15:06:58 -04003672
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003673def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003674 """Parses and converts a PEM-encoded certificate into DER-encoded.
3675
3676 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3677
3678 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003679 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003680 """
3681 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003682 save = False
3683 for line in data.split("\n"):
3684 if "--END CERTIFICATE--" in line:
3685 break
3686 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003687 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003688 if "--BEGIN CERTIFICATE--" in line:
3689 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003690 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003691 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003692
Tao Bao04e1f012018-02-04 12:13:35 -08003693
3694def ExtractPublicKey(cert):
3695 """Extracts the public key (PEM-encoded) from the given certificate file.
3696
3697 Args:
3698 cert: The certificate filename.
3699
3700 Returns:
3701 The public key string.
3702
3703 Raises:
3704 AssertionError: On non-zero return from 'openssl'.
3705 """
3706 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3707 # While openssl 1.1 writes the key into the given filename followed by '-out',
3708 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3709 # stdout instead.
3710 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3711 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3712 pubkey, stderrdata = proc.communicate()
3713 assert proc.returncode == 0, \
3714 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3715 return pubkey
3716
3717
Tao Bao1ac886e2019-06-26 11:58:22 -07003718def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003719 """Extracts the AVB public key from the given public or private key.
3720
3721 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003722 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003723 key: The input key file, which should be PEM-encoded public or private key.
3724
3725 Returns:
3726 The path to the extracted AVB public key file.
3727 """
3728 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3729 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003730 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003731 return output
3732
3733
Doug Zongker412c02f2014-02-13 10:58:24 -08003734def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3735 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003736 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003737
Tao Bao6d5d6232018-03-09 17:04:42 -08003738 Most of the space in the boot and recovery images is just the kernel, which is
3739 identical for the two, so the resulting patch should be efficient. Add it to
3740 the output zip, along with a shell script that is run from init.rc on first
3741 boot to actually do the patching and install the new recovery image.
3742
3743 Args:
3744 input_dir: The top-level input directory of the target-files.zip.
3745 output_sink: The callback function that writes the result.
3746 recovery_img: File object for the recovery image.
3747 boot_img: File objects for the boot image.
3748 info_dict: A dict returned by common.LoadInfoDict() on the input
3749 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003750 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003751 if info_dict is None:
3752 info_dict = OPTIONS.info_dict
3753
Tao Bao6d5d6232018-03-09 17:04:42 -08003754 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003755 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3756
3757 if board_uses_vendorimage:
3758 # In this case, the output sink is rooted at VENDOR
3759 recovery_img_path = "etc/recovery.img"
3760 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3761 sh_dir = "bin"
3762 else:
3763 # In this case the output sink is rooted at SYSTEM
3764 recovery_img_path = "vendor/etc/recovery.img"
3765 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3766 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003767
Tao Baof2cffbd2015-07-22 12:33:18 -07003768 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003769 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003770
3771 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003772 system_root_image = info_dict.get("system_root_image") == "true"
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003773 include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
3774 include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003775 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003776 # With system-root-image, boot and recovery images will have mismatching
3777 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3778 # to handle such a case.
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003779 if system_root_image or include_recovery_dtbo or include_recovery_acpio:
Tao Bao6d5d6232018-03-09 17:04:42 -08003780 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003781 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003782 assert not os.path.exists(path)
3783 else:
3784 diff_program = ["imgdiff"]
3785 if os.path.exists(path):
3786 diff_program.append("-b")
3787 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003788 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003789 else:
3790 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003791
3792 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3793 _, _, patch = d.ComputePatch()
3794 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003795
Dan Albertebb19aa2015-03-27 19:11:53 -07003796 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003797 # The following GetTypeAndDevice()s need to use the path in the target
3798 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003799 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3800 check_no_slot=False)
3801 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3802 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003803 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003804 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003805
Tao Baof2cffbd2015-07-22 12:33:18 -07003806 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003807
3808 # Note that we use /vendor to refer to the recovery resources. This will
3809 # work for a separate vendor partition mounted at /vendor or a
3810 # /system/vendor subdirectory on the system partition, for which init will
3811 # create a symlink from /vendor to /system/vendor.
3812
3813 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003814if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3815 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003816 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003817 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3818 log -t recovery "Installing new recovery image: succeeded" || \\
3819 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003820else
3821 log -t recovery "Recovery image already installed"
3822fi
3823""" % {'type': recovery_type,
3824 'device': recovery_device,
3825 'sha1': recovery_img.sha1,
3826 'size': recovery_img.size}
3827 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003828 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003829if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3830 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003831 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003832 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3833 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3834 log -t recovery "Installing new recovery image: succeeded" || \\
3835 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003836else
3837 log -t recovery "Recovery image already installed"
3838fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003839""" % {'boot_size': boot_img.size,
3840 'boot_sha1': boot_img.sha1,
3841 'recovery_size': recovery_img.size,
3842 'recovery_sha1': recovery_img.sha1,
3843 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003844 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003845 'recovery_type': recovery_type,
3846 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003847 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003848
Bill Peckhame868aec2019-09-17 17:06:47 -07003849 # The install script location moved from /system/etc to /system/bin in the L
3850 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3851 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003852
Tao Bao32fcdab2018-10-12 10:30:39 -07003853 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003854
Tao Baoda30cfa2017-12-01 16:19:46 -08003855 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003856
3857
3858class DynamicPartitionUpdate(object):
3859 def __init__(self, src_group=None, tgt_group=None, progress=None,
3860 block_difference=None):
3861 self.src_group = src_group
3862 self.tgt_group = tgt_group
3863 self.progress = progress
3864 self.block_difference = block_difference
3865
3866 @property
3867 def src_size(self):
3868 if not self.block_difference:
3869 return 0
3870 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3871
3872 @property
3873 def tgt_size(self):
3874 if not self.block_difference:
3875 return 0
3876 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3877
3878 @staticmethod
3879 def _GetSparseImageSize(img):
3880 if not img:
3881 return 0
3882 return img.blocksize * img.total_blocks
3883
3884
3885class DynamicGroupUpdate(object):
3886 def __init__(self, src_size=None, tgt_size=None):
3887 # None: group does not exist. 0: no size limits.
3888 self.src_size = src_size
3889 self.tgt_size = tgt_size
3890
3891
3892class DynamicPartitionsDifference(object):
3893 def __init__(self, info_dict, block_diffs, progress_dict=None,
3894 source_info_dict=None):
3895 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003896 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003897
3898 self._remove_all_before_apply = False
3899 if source_info_dict is None:
3900 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003901 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003902
Tao Baof1113e92019-06-18 12:10:14 -07003903 block_diff_dict = collections.OrderedDict(
3904 [(e.partition, e) for e in block_diffs])
3905
Yifan Hong10c530d2018-12-27 17:34:18 -08003906 assert len(block_diff_dict) == len(block_diffs), \
3907 "Duplicated BlockDifference object for {}".format(
3908 [partition for partition, count in
3909 collections.Counter(e.partition for e in block_diffs).items()
3910 if count > 1])
3911
Yifan Hong79997e52019-01-23 16:56:19 -08003912 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003913
3914 for p, block_diff in block_diff_dict.items():
3915 self._partition_updates[p] = DynamicPartitionUpdate()
3916 self._partition_updates[p].block_difference = block_diff
3917
3918 for p, progress in progress_dict.items():
3919 if p in self._partition_updates:
3920 self._partition_updates[p].progress = progress
3921
3922 tgt_groups = shlex.split(info_dict.get(
3923 "super_partition_groups", "").strip())
3924 src_groups = shlex.split(source_info_dict.get(
3925 "super_partition_groups", "").strip())
3926
3927 for g in tgt_groups:
3928 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003929 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003930 assert p in self._partition_updates, \
3931 "{} is in target super_{}_partition_list but no BlockDifference " \
3932 "object is provided.".format(p, g)
3933 self._partition_updates[p].tgt_group = g
3934
3935 for g in src_groups:
3936 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003937 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003938 assert p in self._partition_updates, \
3939 "{} is in source super_{}_partition_list but no BlockDifference " \
3940 "object is provided.".format(p, g)
3941 self._partition_updates[p].src_group = g
3942
Yifan Hong45433e42019-01-18 13:55:25 -08003943 target_dynamic_partitions = set(shlex.split(info_dict.get(
3944 "dynamic_partition_list", "").strip()))
3945 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3946 if u.tgt_size)
3947 assert block_diffs_with_target == target_dynamic_partitions, \
3948 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3949 list(target_dynamic_partitions), list(block_diffs_with_target))
3950
3951 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3952 "dynamic_partition_list", "").strip()))
3953 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3954 if u.src_size)
3955 assert block_diffs_with_source == source_dynamic_partitions, \
3956 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3957 list(source_dynamic_partitions), list(block_diffs_with_source))
3958
Yifan Hong10c530d2018-12-27 17:34:18 -08003959 if self._partition_updates:
3960 logger.info("Updating dynamic partitions %s",
3961 self._partition_updates.keys())
3962
Yifan Hong79997e52019-01-23 16:56:19 -08003963 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003964
3965 for g in tgt_groups:
3966 self._group_updates[g] = DynamicGroupUpdate()
3967 self._group_updates[g].tgt_size = int(info_dict.get(
3968 "super_%s_group_size" % g, "0").strip())
3969
3970 for g in src_groups:
3971 if g not in self._group_updates:
3972 self._group_updates[g] = DynamicGroupUpdate()
3973 self._group_updates[g].src_size = int(source_info_dict.get(
3974 "super_%s_group_size" % g, "0").strip())
3975
3976 self._Compute()
3977
3978 def WriteScript(self, script, output_zip, write_verify_script=False):
3979 script.Comment('--- Start patching dynamic partitions ---')
3980 for p, u in self._partition_updates.items():
3981 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3982 script.Comment('Patch partition %s' % p)
3983 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3984 write_verify_script=False)
3985
3986 op_list_path = MakeTempFile()
3987 with open(op_list_path, 'w') as f:
3988 for line in self._op_list:
3989 f.write('{}\n'.format(line))
3990
3991 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3992
3993 script.Comment('Update dynamic partition metadata')
3994 script.AppendExtra('assert(update_dynamic_partitions('
3995 'package_extract_file("dynamic_partitions_op_list")));')
3996
3997 if write_verify_script:
3998 for p, u in self._partition_updates.items():
3999 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4000 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04004001 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08004002
4003 for p, u in self._partition_updates.items():
4004 if u.tgt_size and u.src_size <= u.tgt_size:
4005 script.Comment('Patch partition %s' % p)
4006 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
4007 write_verify_script=write_verify_script)
4008 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04004009 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08004010
4011 script.Comment('--- End patching dynamic partitions ---')
4012
4013 def _Compute(self):
4014 self._op_list = list()
4015
4016 def append(line):
4017 self._op_list.append(line)
4018
4019 def comment(line):
4020 self._op_list.append("# %s" % line)
4021
4022 if self._remove_all_before_apply:
4023 comment('Remove all existing dynamic partitions and groups before '
4024 'applying full OTA')
4025 append('remove_all_groups')
4026
4027 for p, u in self._partition_updates.items():
4028 if u.src_group and not u.tgt_group:
4029 append('remove %s' % p)
4030
4031 for p, u in self._partition_updates.items():
4032 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4033 comment('Move partition %s from %s to default' % (p, u.src_group))
4034 append('move %s default' % p)
4035
4036 for p, u in self._partition_updates.items():
4037 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4038 comment('Shrink partition %s from %d to %d' %
4039 (p, u.src_size, u.tgt_size))
4040 append('resize %s %s' % (p, u.tgt_size))
4041
4042 for g, u in self._group_updates.items():
4043 if u.src_size is not None and u.tgt_size is None:
4044 append('remove_group %s' % g)
4045 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04004046 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08004047 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4048 append('resize_group %s %d' % (g, u.tgt_size))
4049
4050 for g, u in self._group_updates.items():
4051 if u.src_size is None and u.tgt_size is not None:
4052 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
4053 append('add_group %s %d' % (g, u.tgt_size))
4054 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04004055 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08004056 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4057 append('resize_group %s %d' % (g, u.tgt_size))
4058
4059 for p, u in self._partition_updates.items():
4060 if u.tgt_group and not u.src_group:
4061 comment('Add partition %s to group %s' % (p, u.tgt_group))
4062 append('add %s %s' % (p, u.tgt_group))
4063
4064 for p, u in self._partition_updates.items():
4065 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04004066 comment('Grow partition %s from %d to %d' %
4067 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08004068 append('resize %s %d' % (p, u.tgt_size))
4069
4070 for p, u in self._partition_updates.items():
4071 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4072 comment('Move partition %s from default to %s' %
4073 (p, u.tgt_group))
4074 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08004075
4076
jiajia tangf3f842b2021-03-17 21:49:44 +08004077def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08004078 """
Yifan Hong85ac5012021-01-07 14:43:46 -08004079 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08004080
4081 Args:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004082 boot_img: the boot image file. Ramdisk must be compressed with lz4 or gzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08004083
4084 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08004085 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08004086 """
Yifan Hongc65a0542021-01-07 14:21:01 -08004087 tmp_dir = MakeTempDir('boot_', suffix='.img')
4088 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04004089 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
4090 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08004091 ramdisk = os.path.join(tmp_dir, 'ramdisk')
4092 if not os.path.isfile(ramdisk):
4093 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
4094 return None
4095 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08004096 if ramdisk_format == RamdiskFormat.LZ4:
4097 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
4098 elif ramdisk_format == RamdiskFormat.GZ:
4099 with open(ramdisk, 'rb') as input_stream:
4100 with open(uncompressed_ramdisk, 'wb') as output_stream:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004101 p2 = Run(['gzip', '-d'], stdin=input_stream.fileno(),
Kelvin Zhang563750f2021-04-28 12:46:17 -04004102 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08004103 p2.wait()
4104 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004105 logger.error('Only support lz4 or gzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08004106 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004107
4108 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
4109 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
4110 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
4111 # the host environment.
4112 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04004113 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08004114
Yifan Hongc65a0542021-01-07 14:21:01 -08004115 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
4116 prop_file = os.path.join(extracted_ramdisk, search_path)
4117 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08004118 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04004119 logger.warning(
4120 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08004121
Yifan Hong7dc51172021-01-12 11:27:39 -08004122 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004123
Yifan Hong85ac5012021-01-07 14:43:46 -08004124 except ExternalError as e:
4125 logger.warning('Unable to get boot image build props: %s', e)
4126 return None
4127
4128
4129def GetBootImageTimestamp(boot_img):
4130 """
4131 Get timestamp from ramdisk within the boot image
4132
4133 Args:
4134 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
4135
4136 Return:
4137 An integer that corresponds to the timestamp of the boot image, or None
4138 if file has unknown format. Raise exception if an unexpected error has
4139 occurred.
4140 """
4141 prop_file = GetBootImageBuildProp(boot_img)
4142 if not prop_file:
4143 return None
4144
4145 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4146 if props is None:
4147 return None
4148
4149 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004150 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4151 if timestamp:
4152 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004153 logger.warning(
4154 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004155 return None
4156
4157 except ExternalError as e:
4158 logger.warning('Unable to get boot image timestamp: %s', e)
4159 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004160
4161
Kelvin Zhang26390482021-11-02 14:31:10 -07004162def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004163 if not os.path.exists(filepath):
4164 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004165 with open(filepath, 'rb') as fp:
4166 # Magic for android sparse image format
4167 # https://source.android.com/devices/bootloader/images
4168 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004169
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004170
Kelvin Zhang22680912023-05-19 13:12:59 -07004171def UnsparseImage(filepath, target_path=None):
4172 if not IsSparseImage(filepath):
4173 return
4174 if target_path is None:
4175 tmp_img = MakeTempFile(suffix=".img")
4176 RunAndCheckOutput(["simg2img", filepath, tmp_img])
4177 os.rename(tmp_img, filepath)
4178 else:
4179 RunAndCheckOutput(["simg2img", filepath, target_path])
4180
4181
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004182def ParseUpdateEngineConfig(path: str):
4183 """Parse the update_engine config stored in file `path`
4184 Args
4185 path: Path to update_engine_config.txt file in target_files
4186
4187 Returns
4188 A tuple of (major, minor) version number . E.g. (2, 8)
4189 """
4190 with open(path, "r") as fp:
4191 # update_engine_config.txt is only supposed to contain two lines,
4192 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
4193 # sufficient. If the length is more than that, something is wrong.
4194 data = fp.read(1024)
4195 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
4196 if not major:
4197 raise ValueError(
4198 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
4199 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
4200 if not minor:
4201 raise ValueError(
4202 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004203 return (int(major.group(1)), int(minor.group(1)))