blob: 826ab1bf8029a722e8ed5003fd409d58a518cf16 [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"):
James Wuc5e321a2023-08-01 17:45:35 +00002432 # Due to ag/24161708, looking for lines such as minSdkVersion:'23',minSdkVersion:'M'
2433 # or sdkVersion:'23', sdkVersion:'M'.
2434 m = re.match(r'(?:minSdkVersion|sdkVersion):\'([^\']*)\'', line)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002435 if m:
2436 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002437 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002438
2439
2440def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002441 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002442
Tao Baof47bf0f2018-03-21 23:28:51 -07002443 If minSdkVersion is set to a codename, it is translated to a number using the
2444 provided map.
2445
2446 Args:
2447 apk_name: The APK filename.
2448
2449 Returns:
2450 The parsed SDK version number.
2451
2452 Raises:
2453 ExternalError: On failing to get the min SDK version number.
2454 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002455 version = GetMinSdkVersion(apk_name)
2456 try:
2457 return int(version)
2458 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002459 # Not a decimal number.
2460 #
2461 # It could be either a straight codename, e.g.
2462 # UpsideDownCake
2463 #
2464 # Or a codename with API fingerprint SHA, e.g.
2465 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2466 #
2467 # Extract the codename and try and map it to a version number.
2468 split = version.split(".")
2469 codename = split[0]
2470 if codename in codename_to_api_level_map:
2471 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002472 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002473 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2474 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002475
2476
2477def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002478 codename_to_api_level_map=None, whole_file=False,
2479 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002480 """Sign the input_name zip/jar/apk, producing output_name. Use the
2481 given key and password (the latter may be None if the key does not
2482 have a password.
2483
Doug Zongker951495f2009-08-14 12:44:19 -07002484 If whole_file is true, use the "-w" option to SignApk to embed a
2485 signature that covers the whole file in the archive comment of the
2486 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002487
2488 min_api_level is the API Level (int) of the oldest platform this file may end
2489 up on. If not specified for an APK, the API Level is obtained by interpreting
2490 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2491
2492 codename_to_api_level_map is needed to translate the codename which may be
2493 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002494
2495 Caller may optionally specify extra args to be passed to SignApk, which
2496 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002497 """
Tao Bao76def242017-11-21 09:25:31 -08002498 if codename_to_api_level_map is None:
2499 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002500 if extra_signapk_args is None:
2501 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002502
Alex Klyubin9667b182015-12-10 13:38:50 -08002503 java_library_path = os.path.join(
2504 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2505
Tao Baoe95540e2016-11-08 12:08:53 -08002506 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2507 ["-Djava.library.path=" + java_library_path,
2508 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002509 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002510 if whole_file:
2511 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002512
2513 min_sdk_version = min_api_level
2514 if min_sdk_version is None:
2515 if not whole_file:
2516 min_sdk_version = GetMinSdkVersionInt(
2517 input_name, codename_to_api_level_map)
2518 if min_sdk_version is not None:
2519 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2520
T.R. Fullhart37e10522013-03-18 10:31:26 -07002521 cmd.extend([key + OPTIONS.public_key_suffix,
2522 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002523 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002524
Tao Bao73dd4f42018-10-04 16:25:33 -07002525 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002526 if password is not None:
2527 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002528 stdoutdata, _ = proc.communicate(password)
2529 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002530 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002531 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002532 proc.returncode, stdoutdata))
2533
Doug Zongkereef39442009-04-02 12:14:19 -07002534
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002535def SignSePolicy(sepolicy, key, password):
2536 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2537 an RSA .sig signature files.
2538 """
2539
2540 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002541 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002542 return False
2543
2544 java_library_path = os.path.join(
2545 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2546
2547 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002548 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002549 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002550 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002551
2552 cmd.extend([key + OPTIONS.public_key_suffix,
2553 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002554 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002555
2556 proc = Run(cmd, stdin=subprocess.PIPE)
2557 if password is not None:
2558 password += "\n"
2559 stdoutdata, _ = proc.communicate(password)
2560 if proc.returncode != 0:
2561 raise ExternalError(
2562 "Failed to run sign sepolicy: return code {}:\n{}".format(
2563 proc.returncode, stdoutdata))
2564 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002565
Kelvin Zhangf294c872022-10-06 14:21:36 -07002566
Doug Zongker37974732010-09-16 17:44:38 -07002567def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002568 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002569
Tao Bao9dd909e2017-11-14 11:27:32 -08002570 For non-AVB images, raise exception if the data is too big. Print a warning
2571 if the data is nearing the maximum size.
2572
2573 For AVB images, the actual image size should be identical to the limit.
2574
2575 Args:
2576 data: A string that contains all the data for the partition.
2577 target: The partition name. The ".img" suffix is optional.
2578 info_dict: The dict to be looked up for relevant info.
2579 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002580 if target.endswith(".img"):
2581 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002582 mount_point = "/" + target
2583
Ying Wangf8824af2014-06-03 14:07:27 -07002584 fs_type = None
2585 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002586 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002587 if mount_point == "/userdata":
2588 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002589 p = info_dict["fstab"][mount_point]
2590 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002591 device = p.device
2592 if "/" in device:
2593 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002594 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002595 if not fs_type or not limit:
2596 return
Doug Zongkereef39442009-04-02 12:14:19 -07002597
Andrew Boie0f9aec82012-02-14 09:32:52 -08002598 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002599 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2600 # path.
2601 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2602 if size != limit:
2603 raise ExternalError(
2604 "Mismatching image size for %s: expected %d actual %d" % (
2605 target, limit, size))
2606 else:
2607 pct = float(size) * 100.0 / limit
2608 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2609 if pct >= 99.0:
2610 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002611
2612 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002613 logger.warning("\n WARNING: %s\n", msg)
2614 else:
2615 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002616
2617
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002618def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002619 """Parses the APK certs info from a given target-files zip.
2620
2621 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2622 tuple with the following elements: (1) a dictionary that maps packages to
2623 certs (based on the "certificate" and "private_key" attributes in the file;
2624 (2) a string representing the extension of compressed APKs in the target files
2625 (e.g ".gz", ".bro").
2626
2627 Args:
2628 tf_zip: The input target_files ZipFile (already open).
2629
2630 Returns:
2631 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2632 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2633 no compressed APKs.
2634 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002635 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002636 compressed_extension = None
2637
Tao Bao0f990332017-09-08 19:02:54 -07002638 # META/apkcerts.txt contains the info for _all_ the packages known at build
2639 # time. Filter out the ones that are not installed.
2640 installed_files = set()
2641 for name in tf_zip.namelist():
2642 basename = os.path.basename(name)
2643 if basename:
2644 installed_files.add(basename)
2645
Tao Baoda30cfa2017-12-01 16:19:46 -08002646 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002647 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002648 if not line:
2649 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002650 m = re.match(
2651 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002652 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2653 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002654 line)
2655 if not m:
2656 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002657
Tao Bao818ddf52018-01-05 11:17:34 -08002658 matches = m.groupdict()
2659 cert = matches["CERT"]
2660 privkey = matches["PRIVKEY"]
2661 name = matches["NAME"]
2662 this_compressed_extension = matches["COMPRESSED"]
2663
2664 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2665 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2666 if cert in SPECIAL_CERT_STRINGS and not privkey:
2667 certmap[name] = cert
2668 elif (cert.endswith(OPTIONS.public_key_suffix) and
2669 privkey.endswith(OPTIONS.private_key_suffix) and
2670 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2671 certmap[name] = cert[:-public_key_suffix_len]
2672 else:
2673 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2674
2675 if not this_compressed_extension:
2676 continue
2677
2678 # Only count the installed files.
2679 filename = name + '.' + this_compressed_extension
2680 if filename not in installed_files:
2681 continue
2682
2683 # Make sure that all the values in the compression map have the same
2684 # extension. We don't support multiple compression methods in the same
2685 # system image.
2686 if compressed_extension:
2687 if this_compressed_extension != compressed_extension:
2688 raise ValueError(
2689 "Multiple compressed extensions: {} vs {}".format(
2690 compressed_extension, this_compressed_extension))
2691 else:
2692 compressed_extension = this_compressed_extension
2693
2694 return (certmap,
2695 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002696
2697
Doug Zongkereef39442009-04-02 12:14:19 -07002698COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002699Global options
2700
2701 -p (--path) <dir>
2702 Prepend <dir>/bin to the list of places to search for binaries run by this
2703 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002704
Doug Zongker05d3dea2009-06-22 11:32:31 -07002705 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002706 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002707
Tao Bao30df8b42018-04-23 15:32:53 -07002708 -x (--extra) <key=value>
2709 Add a key/value pair to the 'extras' dict, which device-specific extension
2710 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002711
Doug Zongkereef39442009-04-02 12:14:19 -07002712 -v (--verbose)
2713 Show command lines being executed.
2714
2715 -h (--help)
2716 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002717
2718 --logfile <file>
2719 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002720"""
2721
Kelvin Zhang0876c412020-06-23 15:06:58 -04002722
Doug Zongkereef39442009-04-02 12:14:19 -07002723def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002724 print(docstring.rstrip("\n"))
2725 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002726
2727
2728def ParseOptions(argv,
2729 docstring,
2730 extra_opts="", extra_long_opts=(),
2731 extra_option_handler=None):
2732 """Parse the options in argv and return any arguments that aren't
2733 flags. docstring is the calling module's docstring, to be displayed
2734 for errors and -h. extra_opts and extra_long_opts are for flags
2735 defined by the caller, which are processed by passing them to
2736 extra_option_handler."""
2737
2738 try:
2739 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002740 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002741 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002742 "signapk_shared_library_path=", "extra_signapk_args=",
2743 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002744 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002745 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2746 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002747 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002748 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002749 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002750 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002751 sys.exit(2)
2752
Doug Zongkereef39442009-04-02 12:14:19 -07002753 for o, a in opts:
2754 if o in ("-h", "--help"):
2755 Usage(docstring)
2756 sys.exit()
2757 elif o in ("-v", "--verbose"):
2758 OPTIONS.verbose = True
2759 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002760 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002761 elif o in ("--signapk_path",):
2762 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002763 elif o in ("--signapk_shared_library_path",):
2764 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002765 elif o in ("--extra_signapk_args",):
2766 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002767 elif o in ("--sign_sepolicy_path",):
2768 OPTIONS.sign_sepolicy_path = a
2769 elif o in ("--extra_sign_sepolicy_args",):
2770 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002771 elif o in ("--aapt2_path",):
2772 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002773 elif o in ("--java_path",):
2774 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002775 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002776 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002777 elif o in ("--android_jar_path",):
2778 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002779 elif o in ("--public_key_suffix",):
2780 OPTIONS.public_key_suffix = a
2781 elif o in ("--private_key_suffix",):
2782 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002783 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002784 raise ValueError(
2785 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002786 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002787 raise ValueError(
2788 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002789 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002790 raise ValueError(
2791 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002792 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002793 raise ValueError(
2794 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002795 elif o in ("-s", "--device_specific"):
2796 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002797 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002798 key, value = a.split("=", 1)
2799 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002800 elif o in ("--logfile",):
2801 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002802 else:
2803 if extra_option_handler is None or not extra_option_handler(o, a):
2804 assert False, "unknown option \"%s\"" % (o,)
2805
Doug Zongker85448772014-09-09 14:59:20 -07002806 if OPTIONS.search_path:
2807 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2808 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002809
2810 return args
2811
2812
Tao Bao4c851b12016-09-19 13:54:38 -07002813def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002814 """Make a temp file and add it to the list of things to be deleted
2815 when Cleanup() is called. Return the filename."""
2816 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2817 os.close(fd)
2818 OPTIONS.tempfiles.append(fn)
2819 return fn
2820
2821
Tao Bao1c830bf2017-12-25 10:43:47 -08002822def MakeTempDir(prefix='tmp', suffix=''):
2823 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2824
2825 Returns:
2826 The absolute pathname of the new directory.
2827 """
2828 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2829 OPTIONS.tempfiles.append(dir_name)
2830 return dir_name
2831
2832
Doug Zongkereef39442009-04-02 12:14:19 -07002833def Cleanup():
2834 for i in OPTIONS.tempfiles:
Kelvin Zhang22680912023-05-19 13:12:59 -07002835 if not os.path.exists(i):
2836 continue
Doug Zongkereef39442009-04-02 12:14:19 -07002837 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002838 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002839 else:
2840 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002841 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002842
2843
2844class PasswordManager(object):
2845 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002846 self.editor = os.getenv("EDITOR")
2847 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002848
2849 def GetPasswords(self, items):
2850 """Get passwords corresponding to each string in 'items',
2851 returning a dict. (The dict may have keys in addition to the
2852 values in 'items'.)
2853
2854 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2855 user edit that file to add more needed passwords. If no editor is
2856 available, or $ANDROID_PW_FILE isn't define, prompts the user
2857 interactively in the ordinary way.
2858 """
2859
2860 current = self.ReadFile()
2861
2862 first = True
2863 while True:
2864 missing = []
2865 for i in items:
2866 if i not in current or not current[i]:
2867 missing.append(i)
2868 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002869 if not missing:
2870 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002871
2872 for i in missing:
2873 current[i] = ""
2874
2875 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002876 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002877 if sys.version_info[0] >= 3:
2878 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002879 answer = raw_input("try to edit again? [y]> ").strip()
2880 if answer and answer[0] not in 'yY':
2881 raise RuntimeError("key passwords unavailable")
2882 first = False
2883
2884 current = self.UpdateAndReadFile(current)
2885
Kelvin Zhang0876c412020-06-23 15:06:58 -04002886 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002887 """Prompt the user to enter a value (password) for each key in
2888 'current' whose value is fales. Returns a new dict with all the
2889 values.
2890 """
2891 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002892 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002893 if v:
2894 result[k] = v
2895 else:
2896 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002897 result[k] = getpass.getpass(
2898 "Enter password for %s key> " % k).strip()
2899 if result[k]:
2900 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002901 return result
2902
2903 def UpdateAndReadFile(self, current):
2904 if not self.editor or not self.pwfile:
2905 return self.PromptResult(current)
2906
2907 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002908 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002909 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2910 f.write("# (Additional spaces are harmless.)\n\n")
2911
2912 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002913 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002914 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002915 f.write("[[[ %s ]]] %s\n" % (v, k))
2916 if not v and first_line is None:
2917 # position cursor on first line with no password.
2918 first_line = i + 4
2919 f.close()
2920
Tao Bao986ee862018-10-04 15:46:16 -07002921 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002922
2923 return self.ReadFile()
2924
2925 def ReadFile(self):
2926 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002927 if self.pwfile is None:
2928 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002929 try:
2930 f = open(self.pwfile, "r")
2931 for line in f:
2932 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002933 if not line or line[0] == '#':
2934 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002935 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2936 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002937 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002938 else:
2939 result[m.group(2)] = m.group(1)
2940 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002941 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002942 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002943 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002944 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002945
2946
Dan Albert8e0178d2015-01-27 15:53:15 -08002947def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2948 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002949
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002950 # http://b/18015246
2951 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2952 # for files larger than 2GiB. We can work around this by adjusting their
2953 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2954 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2955 # it isn't clear to me exactly what circumstances cause this).
2956 # `zipfile.write()` must be used directly to work around this.
2957 #
2958 # This mess can be avoided if we port to python3.
2959 saved_zip64_limit = zipfile.ZIP64_LIMIT
2960 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2961
Dan Albert8e0178d2015-01-27 15:53:15 -08002962 if compress_type is None:
2963 compress_type = zip_file.compression
2964 if arcname is None:
2965 arcname = filename
2966
2967 saved_stat = os.stat(filename)
2968
2969 try:
2970 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2971 # file to be zipped and reset it when we're done.
2972 os.chmod(filename, perms)
2973
2974 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002975 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2976 # intentional. zip stores datetimes in local time without a time zone
2977 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2978 # in the zip archive.
2979 local_epoch = datetime.datetime.fromtimestamp(0)
2980 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002981 os.utime(filename, (timestamp, timestamp))
2982
2983 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2984 finally:
2985 os.chmod(filename, saved_stat.st_mode)
2986 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002987 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08002988
2989
Tao Bao58c1b962015-05-20 09:32:18 -07002990def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002991 compress_type=None):
2992 """Wrap zipfile.writestr() function to work around the zip64 limit.
2993
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002994 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07002995 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2996 when calling crc32(bytes).
2997
2998 But it still works fine to write a shorter string into a large zip file.
2999 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
3000 when we know the string won't be too long.
3001 """
3002
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003003 saved_zip64_limit = zipfile.ZIP64_LIMIT
3004 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3005
Tao Baof3282b42015-04-01 11:21:55 -07003006 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
3007 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07003008 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07003009 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07003010 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08003011 else:
Tao Baof3282b42015-04-01 11:21:55 -07003012 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07003013 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
3014 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
3015 # such a case (since
3016 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
3017 # which seems to make more sense. Otherwise the entry will have 0o000 as the
3018 # permission bits. We follow the logic in Python 3 to get consistent
3019 # behavior between using the two versions.
3020 if not zinfo.external_attr:
3021 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07003022
3023 # If compress_type is given, it overrides the value in zinfo.
3024 if compress_type is not None:
3025 zinfo.compress_type = compress_type
3026
Tao Bao58c1b962015-05-20 09:32:18 -07003027 # If perms is given, it has a priority.
3028 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07003029 # If perms doesn't set the file type, mark it as a regular file.
3030 if perms & 0o770000 == 0:
3031 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07003032 zinfo.external_attr = perms << 16
3033
Tao Baof3282b42015-04-01 11:21:55 -07003034 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07003035 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
3036
Dan Albert8b72aef2015-03-23 19:13:21 -07003037 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003038 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07003039
3040
Kelvin Zhang1caead02022-09-23 10:06:03 -07003041def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08003042 """Deletes entries from a ZIP file.
3043
Tao Bao89d7ab22017-12-14 17:05:33 -08003044 Args:
3045 zip_filename: The name of the ZIP file.
3046 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08003047 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07003048 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08003049 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08003050 # If list is empty, nothing to do
3051 if not entries:
3052 return
Wei Li8895f9e2022-10-10 17:13:17 -07003053
3054 with zipfile.ZipFile(zip_filename, 'r') as zin:
3055 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
3056 raise ExternalError(
3057 "Failed to delete zip entries, name not matched: %s" % entries)
3058
3059 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
3060 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08003061 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
3062 for entry in entries:
3063 cmd.append("-x")
3064 cmd.append(entry)
3065 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07003066
Wei Li8895f9e2022-10-10 17:13:17 -07003067 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08003068
3069
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003070def ZipClose(zip_file):
3071 # http://b/18015246
3072 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
3073 # central directory.
3074 saved_zip64_limit = zipfile.ZIP64_LIMIT
3075 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3076
3077 zip_file.close()
3078
3079 zipfile.ZIP64_LIMIT = saved_zip64_limit
3080
3081
Doug Zongker05d3dea2009-06-22 11:32:31 -07003082class DeviceSpecificParams(object):
3083 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04003084
Doug Zongker05d3dea2009-06-22 11:32:31 -07003085 def __init__(self, **kwargs):
3086 """Keyword arguments to the constructor become attributes of this
3087 object, which is passed to all functions in the device-specific
3088 module."""
Tao Bao38884282019-07-10 22:20:56 -07003089 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07003090 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08003091 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07003092
3093 if self.module is None:
3094 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07003095 if not path:
3096 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003097 try:
3098 if os.path.isdir(path):
3099 info = imp.find_module("releasetools", [path])
3100 else:
3101 d, f = os.path.split(path)
3102 b, x = os.path.splitext(f)
3103 if x == ".py":
3104 f = b
3105 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07003106 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003107 self.module = imp.load_module("device_specific", *info)
3108 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07003109 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003110
3111 def _DoCall(self, function_name, *args, **kwargs):
3112 """Call the named function in the device-specific module, passing
3113 the given args and kwargs. The first argument to the call will be
3114 the DeviceSpecific object itself. If there is no module, or the
3115 module does not define the function, return the value of the
3116 'default' kwarg (which itself defaults to None)."""
3117 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08003118 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003119 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
3120
3121 def FullOTA_Assertions(self):
3122 """Called after emitting the block of assertions at the top of a
3123 full OTA package. Implementations can add whatever additional
3124 assertions they like."""
3125 return self._DoCall("FullOTA_Assertions")
3126
Doug Zongkere5ff5902012-01-17 10:55:37 -08003127 def FullOTA_InstallBegin(self):
3128 """Called at the start of full OTA installation."""
3129 return self._DoCall("FullOTA_InstallBegin")
3130
Yifan Hong10c530d2018-12-27 17:34:18 -08003131 def FullOTA_GetBlockDifferences(self):
3132 """Called during full OTA installation and verification.
3133 Implementation should return a list of BlockDifference objects describing
3134 the update on each additional partitions.
3135 """
3136 return self._DoCall("FullOTA_GetBlockDifferences")
3137
Doug Zongker05d3dea2009-06-22 11:32:31 -07003138 def FullOTA_InstallEnd(self):
3139 """Called at the end of full OTA installation; typically this is
3140 used to install the image for the device's baseband processor."""
3141 return self._DoCall("FullOTA_InstallEnd")
3142
3143 def IncrementalOTA_Assertions(self):
3144 """Called after emitting the block of assertions at the top of an
3145 incremental OTA package. Implementations can add whatever
3146 additional assertions they like."""
3147 return self._DoCall("IncrementalOTA_Assertions")
3148
Doug Zongkere5ff5902012-01-17 10:55:37 -08003149 def IncrementalOTA_VerifyBegin(self):
3150 """Called at the start of the verification phase of incremental
3151 OTA installation; additional checks can be placed here to abort
3152 the script before any changes are made."""
3153 return self._DoCall("IncrementalOTA_VerifyBegin")
3154
Doug Zongker05d3dea2009-06-22 11:32:31 -07003155 def IncrementalOTA_VerifyEnd(self):
3156 """Called at the end of the verification phase of incremental OTA
3157 installation; additional checks can be placed here to abort the
3158 script before any changes are made."""
3159 return self._DoCall("IncrementalOTA_VerifyEnd")
3160
Doug Zongkere5ff5902012-01-17 10:55:37 -08003161 def IncrementalOTA_InstallBegin(self):
3162 """Called at the start of incremental OTA installation (after
3163 verification is complete)."""
3164 return self._DoCall("IncrementalOTA_InstallBegin")
3165
Yifan Hong10c530d2018-12-27 17:34:18 -08003166 def IncrementalOTA_GetBlockDifferences(self):
3167 """Called during incremental OTA installation and verification.
3168 Implementation should return a list of BlockDifference objects describing
3169 the update on each additional partitions.
3170 """
3171 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3172
Doug Zongker05d3dea2009-06-22 11:32:31 -07003173 def IncrementalOTA_InstallEnd(self):
3174 """Called at the end of incremental OTA installation; typically
3175 this is used to install the image for the device's baseband
3176 processor."""
3177 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003178
Tao Bao9bc6bb22015-11-09 16:58:28 -08003179 def VerifyOTA_Assertions(self):
3180 return self._DoCall("VerifyOTA_Assertions")
3181
Tao Bao76def242017-11-21 09:25:31 -08003182
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003183class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003184 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003185 self.name = name
3186 self.data = data
3187 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003188 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003189 self.sha1 = sha1(data).hexdigest()
3190
3191 @classmethod
3192 def FromLocalFile(cls, name, diskname):
3193 f = open(diskname, "rb")
3194 data = f.read()
3195 f.close()
3196 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003197
3198 def WriteToTemp(self):
3199 t = tempfile.NamedTemporaryFile()
3200 t.write(self.data)
3201 t.flush()
3202 return t
3203
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003204 def WriteToDir(self, d):
3205 with open(os.path.join(d, self.name), "wb") as fp:
3206 fp.write(self.data)
3207
Geremy Condra36bd3652014-02-06 19:45:10 -08003208 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003209 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003210
Tao Bao76def242017-11-21 09:25:31 -08003211
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003212DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003213 ".gz": "imgdiff",
3214 ".zip": ["imgdiff", "-z"],
3215 ".jar": ["imgdiff", "-z"],
3216 ".apk": ["imgdiff", "-z"],
3217 ".img": "imgdiff",
3218}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003219
Tao Bao76def242017-11-21 09:25:31 -08003220
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003221class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003222 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003223 self.tf = tf
3224 self.sf = sf
3225 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003226 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003227
3228 def ComputePatch(self):
3229 """Compute the patch (as a string of data) needed to turn sf into
3230 tf. Returns the same tuple as GetPatch()."""
3231
3232 tf = self.tf
3233 sf = self.sf
3234
Doug Zongker24cd2802012-08-14 16:36:15 -07003235 if self.diff_program:
3236 diff_program = self.diff_program
3237 else:
3238 ext = os.path.splitext(tf.name)[1]
3239 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003240
3241 ttemp = tf.WriteToTemp()
3242 stemp = sf.WriteToTemp()
3243
3244 ext = os.path.splitext(tf.name)[1]
3245
3246 try:
3247 ptemp = tempfile.NamedTemporaryFile()
3248 if isinstance(diff_program, list):
3249 cmd = copy.copy(diff_program)
3250 else:
3251 cmd = [diff_program]
3252 cmd.append(stemp.name)
3253 cmd.append(ttemp.name)
3254 cmd.append(ptemp.name)
3255 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003256 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003257
Doug Zongkerf8340082014-08-05 10:39:37 -07003258 def run():
3259 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003260 if e:
3261 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003262 th = threading.Thread(target=run)
3263 th.start()
3264 th.join(timeout=300) # 5 mins
3265 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003266 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003267 p.terminate()
3268 th.join(5)
3269 if th.is_alive():
3270 p.kill()
3271 th.join()
3272
Tianjie Xua2a9f992018-01-05 15:15:54 -08003273 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003274 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003275 self.patch = None
3276 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003277 diff = ptemp.read()
3278 finally:
3279 ptemp.close()
3280 stemp.close()
3281 ttemp.close()
3282
3283 self.patch = diff
3284 return self.tf, self.sf, self.patch
3285
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003286 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003287 """Returns a tuple of (target_file, source_file, patch_data).
3288
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003289 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003290 computing the patch failed.
3291 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003292 return self.tf, self.sf, self.patch
3293
3294
3295def ComputeDifferences(diffs):
3296 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003297 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003298
3299 # Do the largest files first, to try and reduce the long-pole effect.
3300 by_size = [(i.tf.size, i) for i in diffs]
3301 by_size.sort(reverse=True)
3302 by_size = [i[1] for i in by_size]
3303
3304 lock = threading.Lock()
3305 diff_iter = iter(by_size) # accessed under lock
3306
3307 def worker():
3308 try:
3309 lock.acquire()
3310 for d in diff_iter:
3311 lock.release()
3312 start = time.time()
3313 d.ComputePatch()
3314 dur = time.time() - start
3315 lock.acquire()
3316
3317 tf, sf, patch = d.GetPatch()
3318 if sf.name == tf.name:
3319 name = tf.name
3320 else:
3321 name = "%s (%s)" % (tf.name, sf.name)
3322 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003323 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003324 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003325 logger.info(
3326 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3327 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003328 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003329 except Exception:
3330 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003331 raise
3332
3333 # start worker threads; wait for them all to finish.
3334 threads = [threading.Thread(target=worker)
3335 for i in range(OPTIONS.worker_threads)]
3336 for th in threads:
3337 th.start()
3338 while threads:
3339 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003340
3341
Dan Albert8b72aef2015-03-23 19:13:21 -07003342class BlockDifference(object):
3343 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003344 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003345 self.tgt = tgt
3346 self.src = src
3347 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003348 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003349 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003350
Tao Baodd2a5892015-03-12 12:32:37 -07003351 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003352 version = max(
3353 int(i) for i in
3354 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003355 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003356 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003357
Tianjie Xu41976c72019-07-03 13:57:01 -07003358 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3359 version=self.version,
3360 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003361 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003362 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003363 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003364 self.touched_src_ranges = b.touched_src_ranges
3365 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003366
Yifan Hong10c530d2018-12-27 17:34:18 -08003367 # On devices with dynamic partitions, for new partitions,
3368 # src is None but OPTIONS.source_info_dict is not.
3369 if OPTIONS.source_info_dict is None:
3370 is_dynamic_build = OPTIONS.info_dict.get(
3371 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003372 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003373 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003374 is_dynamic_build = OPTIONS.source_info_dict.get(
3375 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003376 is_dynamic_source = partition in shlex.split(
3377 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003378
Yifan Hongbb2658d2019-01-25 12:30:58 -08003379 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003380 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3381
Yifan Hongbb2658d2019-01-25 12:30:58 -08003382 # For dynamic partitions builds, check partition list in both source
3383 # and target build because new partitions may be added, and existing
3384 # partitions may be removed.
3385 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3386
Yifan Hong10c530d2018-12-27 17:34:18 -08003387 if is_dynamic:
3388 self.device = 'map_partition("%s")' % partition
3389 else:
3390 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003391 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3392 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003393 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003394 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3395 OPTIONS.source_info_dict)
3396 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003397
Tao Baod8d14be2016-02-04 14:26:02 -08003398 @property
3399 def required_cache(self):
3400 return self._required_cache
3401
Tao Bao76def242017-11-21 09:25:31 -08003402 def WriteScript(self, script, output_zip, progress=None,
3403 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003404 if not self.src:
3405 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003406 script.Print("Patching %s image unconditionally..." % (self.partition,))
3407 else:
3408 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003409
Dan Albert8b72aef2015-03-23 19:13:21 -07003410 if progress:
3411 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003412 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003413
3414 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003415 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003416
Tao Bao9bc6bb22015-11-09 16:58:28 -08003417 def WriteStrictVerifyScript(self, script):
3418 """Verify all the blocks in the care_map, including clobbered blocks.
3419
3420 This differs from the WriteVerifyScript() function: a) it prints different
3421 error messages; b) it doesn't allow half-way updated images to pass the
3422 verification."""
3423
3424 partition = self.partition
3425 script.Print("Verifying %s..." % (partition,))
3426 ranges = self.tgt.care_map
3427 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003428 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003429 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3430 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003431 self.device, ranges_str,
3432 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003433 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003434 script.AppendExtra("")
3435
Tao Baod522bdc2016-04-12 15:53:16 -07003436 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003437 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003438
3439 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003440 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003441 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003442
3443 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003444 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003445 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003446 ranges = self.touched_src_ranges
3447 expected_sha1 = self.touched_src_sha1
3448 else:
3449 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3450 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003451
3452 # No blocks to be checked, skipping.
3453 if not ranges:
3454 return
3455
Tao Bao5ece99d2015-05-12 11:42:31 -07003456 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003457 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003458 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003459 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3460 '"%s.patch.dat")) then' % (
3461 self.device, ranges_str, expected_sha1,
3462 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003463 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003464 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003465
Tianjie Xufc3422a2015-12-15 11:53:59 -08003466 if self.version >= 4:
3467
3468 # Bug: 21124327
3469 # When generating incrementals for the system and vendor partitions in
3470 # version 4 or newer, explicitly check the first block (which contains
3471 # the superblock) of the partition to see if it's what we expect. If
3472 # this check fails, give an explicit log message about the partition
3473 # having been remounted R/W (the most likely explanation).
3474 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003475 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003476
3477 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003478 if partition == "system":
3479 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3480 else:
3481 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003482 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003483 'ifelse (block_image_recover({device}, "{ranges}") && '
3484 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003485 'package_extract_file("{partition}.transfer.list"), '
3486 '"{partition}.new.dat", "{partition}.patch.dat"), '
3487 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003488 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003489 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003490 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003491
Tao Baodd2a5892015-03-12 12:32:37 -07003492 # Abort the OTA update. Note that the incremental OTA cannot be applied
3493 # even if it may match the checksum of the target partition.
3494 # a) If version < 3, operations like move and erase will make changes
3495 # unconditionally and damage the partition.
3496 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003497 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003498 if partition == "system":
3499 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3500 else:
3501 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3502 script.AppendExtra((
3503 'abort("E%d: %s partition has unexpected contents");\n'
3504 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003505
Yifan Hong10c530d2018-12-27 17:34:18 -08003506 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003507 partition = self.partition
3508 script.Print('Verifying the updated %s image...' % (partition,))
3509 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3510 ranges = self.tgt.care_map
3511 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003512 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003513 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003514 self.device, ranges_str,
3515 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003516
3517 # Bug: 20881595
3518 # Verify that extended blocks are really zeroed out.
3519 if self.tgt.extended:
3520 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003521 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003522 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003523 self.device, ranges_str,
3524 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003525 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003526 if partition == "system":
3527 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3528 else:
3529 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003530 script.AppendExtra(
3531 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003532 ' abort("E%d: %s partition has unexpected non-zero contents after '
3533 'OTA update");\n'
3534 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003535 else:
3536 script.Print('Verified the updated %s image.' % (partition,))
3537
Tianjie Xu209db462016-05-24 17:34:52 -07003538 if partition == "system":
3539 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3540 else:
3541 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3542
Tao Bao5fcaaef2015-06-01 13:40:49 -07003543 script.AppendExtra(
3544 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003545 ' abort("E%d: %s partition has unexpected contents after OTA '
3546 'update");\n'
3547 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003548
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003549 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003550 ZipWrite(output_zip,
3551 '{}.transfer.list'.format(self.path),
3552 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003553
Tao Bao76def242017-11-21 09:25:31 -08003554 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3555 # its size. Quailty 9 almost triples the compression time but doesn't
3556 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003557 # zip | brotli(quality 6) | brotli(quality 9)
3558 # compressed_size: 942M | 869M (~8% reduced) | 854M
3559 # compression_time: 75s | 265s | 719s
3560 # decompression_time: 15s | 25s | 25s
3561
3562 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003563 brotli_cmd = ['brotli', '--quality=6',
3564 '--output={}.new.dat.br'.format(self.path),
3565 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003566 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003567 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003568
3569 new_data_name = '{}.new.dat.br'.format(self.partition)
3570 ZipWrite(output_zip,
3571 '{}.new.dat.br'.format(self.path),
3572 new_data_name,
3573 compress_type=zipfile.ZIP_STORED)
3574 else:
3575 new_data_name = '{}.new.dat'.format(self.partition)
3576 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3577
Dan Albert8e0178d2015-01-27 15:53:15 -08003578 ZipWrite(output_zip,
3579 '{}.patch.dat'.format(self.path),
3580 '{}.patch.dat'.format(self.partition),
3581 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003582
Tianjie Xu209db462016-05-24 17:34:52 -07003583 if self.partition == "system":
3584 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3585 else:
3586 code = ErrorCode.VENDOR_UPDATE_FAILURE
3587
Yifan Hong10c530d2018-12-27 17:34:18 -08003588 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003589 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003590 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003591 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003592 device=self.device, partition=self.partition,
3593 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003594 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003595
Kelvin Zhang0876c412020-06-23 15:06:58 -04003596 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003597 data = source.ReadRangeSet(ranges)
3598 ctx = sha1()
3599
3600 for p in data:
3601 ctx.update(p)
3602
3603 return ctx.hexdigest()
3604
Kelvin Zhang0876c412020-06-23 15:06:58 -04003605 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003606 """Return the hash value for all zero blocks."""
3607 zero_block = '\x00' * 4096
3608 ctx = sha1()
3609 for _ in range(num_blocks):
3610 ctx.update(zero_block)
3611
3612 return ctx.hexdigest()
3613
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003614
Tianjie Xu41976c72019-07-03 13:57:01 -07003615# Expose these two classes to support vendor-specific scripts
3616DataImage = images.DataImage
3617EmptyImage = images.EmptyImage
3618
Tao Bao76def242017-11-21 09:25:31 -08003619
Doug Zongker96a57e72010-09-26 14:57:41 -07003620# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003621PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003622 "ext4": "EMMC",
3623 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003624 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003625 "squashfs": "EMMC",
3626 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003627}
Doug Zongker96a57e72010-09-26 14:57:41 -07003628
Kelvin Zhang0876c412020-06-23 15:06:58 -04003629
Yifan Hongbdb32012020-05-07 12:38:53 -07003630def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3631 """
3632 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3633 backwards compatibility. It aborts if the fstab entry has slotselect option
3634 (unless check_no_slot is explicitly set to False).
3635 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003636 fstab = info["fstab"]
3637 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003638 if check_no_slot:
3639 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003640 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003641 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3642 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003643 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003644
3645
Yifan Hongbdb32012020-05-07 12:38:53 -07003646def GetTypeAndDeviceExpr(mount_point, info):
3647 """
3648 Return the filesystem of the partition, and an edify expression that evaluates
3649 to the device at runtime.
3650 """
3651 fstab = info["fstab"]
3652 if fstab:
3653 p = fstab[mount_point]
3654 device_expr = '"%s"' % fstab[mount_point].device
3655 if p.slotselect:
3656 device_expr = 'add_slot_suffix(%s)' % device_expr
3657 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003658 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003659
3660
3661def GetEntryForDevice(fstab, device):
3662 """
3663 Returns:
3664 The first entry in fstab whose device is the given value.
3665 """
3666 if not fstab:
3667 return None
3668 for mount_point in fstab:
3669 if fstab[mount_point].device == device:
3670 return fstab[mount_point]
3671 return None
3672
Kelvin Zhang0876c412020-06-23 15:06:58 -04003673
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003674def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003675 """Parses and converts a PEM-encoded certificate into DER-encoded.
3676
3677 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3678
3679 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003680 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003681 """
3682 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003683 save = False
3684 for line in data.split("\n"):
3685 if "--END CERTIFICATE--" in line:
3686 break
3687 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003688 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003689 if "--BEGIN CERTIFICATE--" in line:
3690 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003691 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003692 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003693
Tao Bao04e1f012018-02-04 12:13:35 -08003694
3695def ExtractPublicKey(cert):
3696 """Extracts the public key (PEM-encoded) from the given certificate file.
3697
3698 Args:
3699 cert: The certificate filename.
3700
3701 Returns:
3702 The public key string.
3703
3704 Raises:
3705 AssertionError: On non-zero return from 'openssl'.
3706 """
3707 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3708 # While openssl 1.1 writes the key into the given filename followed by '-out',
3709 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3710 # stdout instead.
3711 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3712 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3713 pubkey, stderrdata = proc.communicate()
3714 assert proc.returncode == 0, \
3715 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3716 return pubkey
3717
3718
Tao Bao1ac886e2019-06-26 11:58:22 -07003719def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003720 """Extracts the AVB public key from the given public or private key.
3721
3722 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003723 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003724 key: The input key file, which should be PEM-encoded public or private key.
3725
3726 Returns:
3727 The path to the extracted AVB public key file.
3728 """
3729 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3730 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003731 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003732 return output
3733
3734
Doug Zongker412c02f2014-02-13 10:58:24 -08003735def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3736 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003737 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003738
Tao Bao6d5d6232018-03-09 17:04:42 -08003739 Most of the space in the boot and recovery images is just the kernel, which is
3740 identical for the two, so the resulting patch should be efficient. Add it to
3741 the output zip, along with a shell script that is run from init.rc on first
3742 boot to actually do the patching and install the new recovery image.
3743
3744 Args:
3745 input_dir: The top-level input directory of the target-files.zip.
3746 output_sink: The callback function that writes the result.
3747 recovery_img: File object for the recovery image.
3748 boot_img: File objects for the boot image.
3749 info_dict: A dict returned by common.LoadInfoDict() on the input
3750 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003751 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003752 if info_dict is None:
3753 info_dict = OPTIONS.info_dict
3754
Tao Bao6d5d6232018-03-09 17:04:42 -08003755 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003756 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3757
3758 if board_uses_vendorimage:
3759 # In this case, the output sink is rooted at VENDOR
3760 recovery_img_path = "etc/recovery.img"
3761 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3762 sh_dir = "bin"
3763 else:
3764 # In this case the output sink is rooted at SYSTEM
3765 recovery_img_path = "vendor/etc/recovery.img"
3766 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3767 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003768
Tao Baof2cffbd2015-07-22 12:33:18 -07003769 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003770 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003771
3772 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003773 system_root_image = info_dict.get("system_root_image") == "true"
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003774 include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
3775 include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003776 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003777 # With system-root-image, boot and recovery images will have mismatching
3778 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3779 # to handle such a case.
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003780 if system_root_image or include_recovery_dtbo or include_recovery_acpio:
Tao Bao6d5d6232018-03-09 17:04:42 -08003781 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003782 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003783 assert not os.path.exists(path)
3784 else:
3785 diff_program = ["imgdiff"]
3786 if os.path.exists(path):
3787 diff_program.append("-b")
3788 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003789 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003790 else:
3791 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003792
3793 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3794 _, _, patch = d.ComputePatch()
3795 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003796
Dan Albertebb19aa2015-03-27 19:11:53 -07003797 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003798 # The following GetTypeAndDevice()s need to use the path in the target
3799 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003800 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3801 check_no_slot=False)
3802 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3803 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003804 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003805 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003806
Tao Baof2cffbd2015-07-22 12:33:18 -07003807 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003808
3809 # Note that we use /vendor to refer to the recovery resources. This will
3810 # work for a separate vendor partition mounted at /vendor or a
3811 # /system/vendor subdirectory on the system partition, for which init will
3812 # create a symlink from /vendor to /system/vendor.
3813
3814 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003815if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3816 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003817 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003818 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3819 log -t recovery "Installing new recovery image: succeeded" || \\
3820 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003821else
3822 log -t recovery "Recovery image already installed"
3823fi
3824""" % {'type': recovery_type,
3825 'device': recovery_device,
3826 'sha1': recovery_img.sha1,
3827 'size': recovery_img.size}
3828 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003829 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003830if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3831 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003832 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003833 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3834 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3835 log -t recovery "Installing new recovery image: succeeded" || \\
3836 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003837else
3838 log -t recovery "Recovery image already installed"
3839fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003840""" % {'boot_size': boot_img.size,
3841 'boot_sha1': boot_img.sha1,
3842 'recovery_size': recovery_img.size,
3843 'recovery_sha1': recovery_img.sha1,
3844 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003845 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003846 'recovery_type': recovery_type,
3847 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003848 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003849
Bill Peckhame868aec2019-09-17 17:06:47 -07003850 # The install script location moved from /system/etc to /system/bin in the L
3851 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3852 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003853
Tao Bao32fcdab2018-10-12 10:30:39 -07003854 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003855
Tao Baoda30cfa2017-12-01 16:19:46 -08003856 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003857
3858
3859class DynamicPartitionUpdate(object):
3860 def __init__(self, src_group=None, tgt_group=None, progress=None,
3861 block_difference=None):
3862 self.src_group = src_group
3863 self.tgt_group = tgt_group
3864 self.progress = progress
3865 self.block_difference = block_difference
3866
3867 @property
3868 def src_size(self):
3869 if not self.block_difference:
3870 return 0
3871 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3872
3873 @property
3874 def tgt_size(self):
3875 if not self.block_difference:
3876 return 0
3877 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3878
3879 @staticmethod
3880 def _GetSparseImageSize(img):
3881 if not img:
3882 return 0
3883 return img.blocksize * img.total_blocks
3884
3885
3886class DynamicGroupUpdate(object):
3887 def __init__(self, src_size=None, tgt_size=None):
3888 # None: group does not exist. 0: no size limits.
3889 self.src_size = src_size
3890 self.tgt_size = tgt_size
3891
3892
3893class DynamicPartitionsDifference(object):
3894 def __init__(self, info_dict, block_diffs, progress_dict=None,
3895 source_info_dict=None):
3896 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003897 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003898
3899 self._remove_all_before_apply = False
3900 if source_info_dict is None:
3901 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003902 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003903
Tao Baof1113e92019-06-18 12:10:14 -07003904 block_diff_dict = collections.OrderedDict(
3905 [(e.partition, e) for e in block_diffs])
3906
Yifan Hong10c530d2018-12-27 17:34:18 -08003907 assert len(block_diff_dict) == len(block_diffs), \
3908 "Duplicated BlockDifference object for {}".format(
3909 [partition for partition, count in
3910 collections.Counter(e.partition for e in block_diffs).items()
3911 if count > 1])
3912
Yifan Hong79997e52019-01-23 16:56:19 -08003913 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003914
3915 for p, block_diff in block_diff_dict.items():
3916 self._partition_updates[p] = DynamicPartitionUpdate()
3917 self._partition_updates[p].block_difference = block_diff
3918
3919 for p, progress in progress_dict.items():
3920 if p in self._partition_updates:
3921 self._partition_updates[p].progress = progress
3922
3923 tgt_groups = shlex.split(info_dict.get(
3924 "super_partition_groups", "").strip())
3925 src_groups = shlex.split(source_info_dict.get(
3926 "super_partition_groups", "").strip())
3927
3928 for g in tgt_groups:
3929 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003930 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003931 assert p in self._partition_updates, \
3932 "{} is in target super_{}_partition_list but no BlockDifference " \
3933 "object is provided.".format(p, g)
3934 self._partition_updates[p].tgt_group = g
3935
3936 for g in src_groups:
3937 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003938 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003939 assert p in self._partition_updates, \
3940 "{} is in source super_{}_partition_list but no BlockDifference " \
3941 "object is provided.".format(p, g)
3942 self._partition_updates[p].src_group = g
3943
Yifan Hong45433e42019-01-18 13:55:25 -08003944 target_dynamic_partitions = set(shlex.split(info_dict.get(
3945 "dynamic_partition_list", "").strip()))
3946 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3947 if u.tgt_size)
3948 assert block_diffs_with_target == target_dynamic_partitions, \
3949 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3950 list(target_dynamic_partitions), list(block_diffs_with_target))
3951
3952 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3953 "dynamic_partition_list", "").strip()))
3954 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3955 if u.src_size)
3956 assert block_diffs_with_source == source_dynamic_partitions, \
3957 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3958 list(source_dynamic_partitions), list(block_diffs_with_source))
3959
Yifan Hong10c530d2018-12-27 17:34:18 -08003960 if self._partition_updates:
3961 logger.info("Updating dynamic partitions %s",
3962 self._partition_updates.keys())
3963
Yifan Hong79997e52019-01-23 16:56:19 -08003964 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003965
3966 for g in tgt_groups:
3967 self._group_updates[g] = DynamicGroupUpdate()
3968 self._group_updates[g].tgt_size = int(info_dict.get(
3969 "super_%s_group_size" % g, "0").strip())
3970
3971 for g in src_groups:
3972 if g not in self._group_updates:
3973 self._group_updates[g] = DynamicGroupUpdate()
3974 self._group_updates[g].src_size = int(source_info_dict.get(
3975 "super_%s_group_size" % g, "0").strip())
3976
3977 self._Compute()
3978
3979 def WriteScript(self, script, output_zip, write_verify_script=False):
3980 script.Comment('--- Start patching dynamic partitions ---')
3981 for p, u in self._partition_updates.items():
3982 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3983 script.Comment('Patch partition %s' % p)
3984 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3985 write_verify_script=False)
3986
3987 op_list_path = MakeTempFile()
3988 with open(op_list_path, 'w') as f:
3989 for line in self._op_list:
3990 f.write('{}\n'.format(line))
3991
3992 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3993
3994 script.Comment('Update dynamic partition metadata')
3995 script.AppendExtra('assert(update_dynamic_partitions('
3996 'package_extract_file("dynamic_partitions_op_list")));')
3997
3998 if write_verify_script:
3999 for p, u in self._partition_updates.items():
4000 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4001 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04004002 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08004003
4004 for p, u in self._partition_updates.items():
4005 if u.tgt_size and u.src_size <= u.tgt_size:
4006 script.Comment('Patch partition %s' % p)
4007 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
4008 write_verify_script=write_verify_script)
4009 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04004010 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08004011
4012 script.Comment('--- End patching dynamic partitions ---')
4013
4014 def _Compute(self):
4015 self._op_list = list()
4016
4017 def append(line):
4018 self._op_list.append(line)
4019
4020 def comment(line):
4021 self._op_list.append("# %s" % line)
4022
4023 if self._remove_all_before_apply:
4024 comment('Remove all existing dynamic partitions and groups before '
4025 'applying full OTA')
4026 append('remove_all_groups')
4027
4028 for p, u in self._partition_updates.items():
4029 if u.src_group and not u.tgt_group:
4030 append('remove %s' % p)
4031
4032 for p, u in self._partition_updates.items():
4033 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4034 comment('Move partition %s from %s to default' % (p, u.src_group))
4035 append('move %s default' % p)
4036
4037 for p, u in self._partition_updates.items():
4038 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4039 comment('Shrink partition %s from %d to %d' %
4040 (p, u.src_size, u.tgt_size))
4041 append('resize %s %s' % (p, u.tgt_size))
4042
4043 for g, u in self._group_updates.items():
4044 if u.src_size is not None and u.tgt_size is None:
4045 append('remove_group %s' % g)
4046 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04004047 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08004048 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4049 append('resize_group %s %d' % (g, u.tgt_size))
4050
4051 for g, u in self._group_updates.items():
4052 if u.src_size is None and u.tgt_size is not None:
4053 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
4054 append('add_group %s %d' % (g, u.tgt_size))
4055 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04004056 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08004057 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4058 append('resize_group %s %d' % (g, u.tgt_size))
4059
4060 for p, u in self._partition_updates.items():
4061 if u.tgt_group and not u.src_group:
4062 comment('Add partition %s to group %s' % (p, u.tgt_group))
4063 append('add %s %s' % (p, u.tgt_group))
4064
4065 for p, u in self._partition_updates.items():
4066 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04004067 comment('Grow partition %s from %d to %d' %
4068 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08004069 append('resize %s %d' % (p, u.tgt_size))
4070
4071 for p, u in self._partition_updates.items():
4072 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4073 comment('Move partition %s from default to %s' %
4074 (p, u.tgt_group))
4075 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08004076
4077
jiajia tangf3f842b2021-03-17 21:49:44 +08004078def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08004079 """
Yifan Hong85ac5012021-01-07 14:43:46 -08004080 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08004081
4082 Args:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004083 boot_img: the boot image file. Ramdisk must be compressed with lz4 or gzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08004084
4085 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08004086 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08004087 """
Yifan Hongc65a0542021-01-07 14:21:01 -08004088 tmp_dir = MakeTempDir('boot_', suffix='.img')
4089 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04004090 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
4091 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08004092 ramdisk = os.path.join(tmp_dir, 'ramdisk')
4093 if not os.path.isfile(ramdisk):
4094 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
4095 return None
4096 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08004097 if ramdisk_format == RamdiskFormat.LZ4:
4098 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
4099 elif ramdisk_format == RamdiskFormat.GZ:
4100 with open(ramdisk, 'rb') as input_stream:
4101 with open(uncompressed_ramdisk, 'wb') as output_stream:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004102 p2 = Run(['gzip', '-d'], stdin=input_stream.fileno(),
Kelvin Zhang563750f2021-04-28 12:46:17 -04004103 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08004104 p2.wait()
4105 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004106 logger.error('Only support lz4 or gzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08004107 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004108
4109 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
4110 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
4111 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
4112 # the host environment.
4113 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04004114 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08004115
Yifan Hongc65a0542021-01-07 14:21:01 -08004116 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
4117 prop_file = os.path.join(extracted_ramdisk, search_path)
4118 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08004119 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04004120 logger.warning(
4121 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08004122
Yifan Hong7dc51172021-01-12 11:27:39 -08004123 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004124
Yifan Hong85ac5012021-01-07 14:43:46 -08004125 except ExternalError as e:
4126 logger.warning('Unable to get boot image build props: %s', e)
4127 return None
4128
4129
4130def GetBootImageTimestamp(boot_img):
4131 """
4132 Get timestamp from ramdisk within the boot image
4133
4134 Args:
4135 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
4136
4137 Return:
4138 An integer that corresponds to the timestamp of the boot image, or None
4139 if file has unknown format. Raise exception if an unexpected error has
4140 occurred.
4141 """
4142 prop_file = GetBootImageBuildProp(boot_img)
4143 if not prop_file:
4144 return None
4145
4146 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4147 if props is None:
4148 return None
4149
4150 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004151 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4152 if timestamp:
4153 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004154 logger.warning(
4155 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004156 return None
4157
4158 except ExternalError as e:
4159 logger.warning('Unable to get boot image timestamp: %s', e)
4160 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004161
4162
Kelvin Zhang26390482021-11-02 14:31:10 -07004163def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004164 if not os.path.exists(filepath):
4165 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004166 with open(filepath, 'rb') as fp:
4167 # Magic for android sparse image format
4168 # https://source.android.com/devices/bootloader/images
4169 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004170
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004171
Kelvin Zhang22680912023-05-19 13:12:59 -07004172def UnsparseImage(filepath, target_path=None):
4173 if not IsSparseImage(filepath):
4174 return
4175 if target_path is None:
4176 tmp_img = MakeTempFile(suffix=".img")
4177 RunAndCheckOutput(["simg2img", filepath, tmp_img])
4178 os.rename(tmp_img, filepath)
4179 else:
4180 RunAndCheckOutput(["simg2img", filepath, target_path])
4181
4182
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004183def ParseUpdateEngineConfig(path: str):
4184 """Parse the update_engine config stored in file `path`
4185 Args
4186 path: Path to update_engine_config.txt file in target_files
4187
4188 Returns
4189 A tuple of (major, minor) version number . E.g. (2, 8)
4190 """
4191 with open(path, "r") as fp:
4192 # update_engine_config.txt is only supposed to contain two lines,
4193 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
4194 # sufficient. If the length is more than that, something is wrong.
4195 data = fp.read(1024)
4196 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
4197 if not major:
4198 raise ValueError(
4199 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
4200 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
4201 if not minor:
4202 raise ValueError(
4203 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004204 return (int(major.group(1)), int(minor.group(1)))