blob: 4e783fff7e4ed6f7fa8780e4064f52a9e184b10d [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
38import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070039import threading
40import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070041import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080042from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070043
Tianjie Xu41976c72019-07-03 13:57:01 -070044import images
Kelvin Zhang27324132021-03-22 15:38:38 -040045import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080046import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070047from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070048
Tao Bao32fcdab2018-10-12 10:30:39 -070049logger = logging.getLogger(__name__)
50
Tao Bao986ee862018-10-04 15:46:16 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070053
Dan Albert8b72aef2015-03-23 19:13:21 -070054 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070055 # Set up search path, in order to find framework/ and lib64/. At the time of
56 # running this function, user-supplied search path (`--path`) hasn't been
57 # available. So the value set here is the default, which might be overridden
58 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040059 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070060 if exec_path.endswith('.py'):
61 script_name = os.path.basename(exec_path)
62 # logger hasn't been initialized yet at this point. Use print to output
63 # warnings.
64 print(
65 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040066 'executable -- build and run `{}` directly.'.format(
67 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070068 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040069 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030070
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080072 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
73 if "ANDROID_HOST_OUT" in os.environ:
74 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080075 self.signapk_shared_library_path = "lib64" # Relative to search_path
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020076 self.sign_sepolicy_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.extra_signapk_args = []
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020078 self.extra_sign_sepolicy_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000079 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070080 self.java_path = "java" # Use the one on the path by default.
Sorin Basca05085832022-09-14 11:33:22 +010081 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080082 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070083 self.public_key_suffix = ".x509.pem"
84 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070085 # use otatools built boot_signer by default
Dan Albert8b72aef2015-03-23 19:13:21 -070086 self.verbose = False
87 self.tempfiles = []
88 self.device_specific = None
89 self.extras = {}
90 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070091 self.source_info_dict = None
92 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070093 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070094 # Stash size cannot exceed cache_size * threshold.
95 self.cache_size = None
96 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070097 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070098 self.host_tools = {}
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
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhang563750f2021-04-28 12:46:17 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhang563750f2021-04-28 12:46:17 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Kelvin Zhang766eea72021-06-03 09:36:08 -0400279 if verbose is None:
280 verbose = OPTIONS.verbose
281
Tao Bao32fcdab2018-10-12 10:30:39 -0700282 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400283 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700285 return subprocess.Popen(args, **kwargs)
286
287
Tao Bao986ee862018-10-04 15:46:16 -0700288def RunAndCheckOutput(args, verbose=None, **kwargs):
289 """Runs the given command and returns the output.
290
291 Args:
292 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700293 verbose: Whether the commands should be shown. Default to the global
294 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700295 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
296 stdin, etc. stdout and stderr will default to subprocess.PIPE and
297 subprocess.STDOUT respectively unless caller specifies any of them.
298
299 Returns:
300 The output string.
301
302 Raises:
303 ExternalError: On non-zero exit from the command.
304 """
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -0800305 if verbose is None:
306 verbose = OPTIONS.verbose
Tao Bao986ee862018-10-04 15:46:16 -0700307 proc = Run(args, verbose=verbose, **kwargs)
308 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800309 if output is None:
310 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700311 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400312 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700313 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700314 if proc.returncode != 0:
315 raise ExternalError(
316 "Failed to run command '{}' (exit code {}):\n{}".format(
317 args, proc.returncode, output))
318 return output
319
320
Tao Baoc765cca2018-01-31 17:32:40 -0800321def RoundUpTo4K(value):
322 rounded_up = value + 4095
323 return rounded_up - (rounded_up % 4096)
324
325
Ying Wang7e6d4e42010-12-13 16:25:36 -0800326def CloseInheritedPipes():
327 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
328 before doing other work."""
329 if platform.system() != "Darwin":
330 return
331 for d in range(3, 1025):
332 try:
333 stat = os.fstat(d)
334 if stat is not None:
335 pipebit = stat[0] & 0x1000
336 if pipebit != 0:
337 os.close(d)
338 except OSError:
339 pass
340
341
Tao Bao1c320f82019-10-04 23:25:12 -0700342class BuildInfo(object):
343 """A class that holds the information for a given build.
344
345 This class wraps up the property querying for a given source or target build.
346 It abstracts away the logic of handling OEM-specific properties, and caches
347 the commonly used properties such as fingerprint.
348
349 There are two types of info dicts: a) build-time info dict, which is generated
350 at build time (i.e. included in a target_files zip); b) OEM info dict that is
351 specified at package generation time (via command line argument
352 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
353 having "oem_fingerprint_properties" in build-time info dict), all the queries
354 would be answered based on build-time info dict only. Otherwise if using
355 OEM-specific properties, some of them will be calculated from two info dicts.
356
357 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800358 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700359
360 Attributes:
361 info_dict: The build-time info dict.
362 is_ab: Whether it's a build that uses A/B OTA.
363 oem_dicts: A list of OEM dicts.
364 oem_props: A list of OEM properties that should be read from OEM dicts; None
365 if the build doesn't use any OEM-specific property.
366 fingerprint: The fingerprint of the build, which would be calculated based
367 on OEM properties if applicable.
368 device: The device name, which could come from OEM dicts if applicable.
369 """
370
371 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
372 "ro.product.manufacturer", "ro.product.model",
373 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700374 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
375 "product", "odm", "vendor", "system_ext", "system"]
376 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
377 "product", "product_services", "odm", "vendor", "system"]
378 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700379
Tianjiefdda51d2021-05-05 14:46:35 -0700380 # The length of vbmeta digest to append to the fingerprint
381 _VBMETA_DIGEST_SIZE_USED = 8
382
383 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700384 """Initializes a BuildInfo instance with the given dicts.
385
386 Note that it only wraps up the given dicts, without making copies.
387
388 Arguments:
389 info_dict: The build-time info dict.
390 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
391 that it always uses the first dict to calculate the fingerprint or the
392 device name. The rest would be used for asserting OEM properties only
393 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700394 use_legacy_id: Use the legacy build id to construct the fingerprint. This
395 is used when we need a BuildInfo class, while the vbmeta digest is
396 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700397
398 Raises:
399 ValueError: On invalid inputs.
400 """
401 self.info_dict = info_dict
402 self.oem_dicts = oem_dicts
403
404 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700405 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700406
Hongguang Chend7c160f2020-05-03 21:24:26 -0700407 # Skip _oem_props if oem_dicts is None to use BuildInfo in
408 # sign_target_files_apks
409 if self.oem_dicts:
410 self._oem_props = info_dict.get("oem_fingerprint_properties")
411 else:
412 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700413
Daniel Normand5fe8622020-01-08 17:01:11 -0800414 def check_fingerprint(fingerprint):
415 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
416 raise ValueError(
417 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
418 "3.2.2. Build Parameters.".format(fingerprint))
419
Daniel Normand5fe8622020-01-08 17:01:11 -0800420 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800421 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800422 try:
423 fingerprint = self.CalculatePartitionFingerprint(partition)
424 check_fingerprint(fingerprint)
425 self._partition_fingerprints[partition] = fingerprint
426 except ExternalError:
427 continue
428 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800429 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800430 # need a fingerprint when creating the image.
431 self._partition_fingerprints[
432 "system_other"] = self._partition_fingerprints["system"]
433
Tao Bao1c320f82019-10-04 23:25:12 -0700434 # These two should be computed only after setting self._oem_props.
435 self._device = self.GetOemProperty("ro.product.device")
436 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800437 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700438
439 @property
440 def is_ab(self):
441 return self._is_ab
442
443 @property
444 def device(self):
445 return self._device
446
447 @property
448 def fingerprint(self):
449 return self._fingerprint
450
451 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400452 def is_vabc(self):
453 vendor_prop = self.info_dict.get("vendor.build.prop")
454 vabc_enabled = vendor_prop and \
455 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
456 return vabc_enabled
457
458 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700459 def is_android_r(self):
460 system_prop = self.info_dict.get("system.build.prop")
461 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
462
463 @property
David Anderson1c596172023-04-14 16:01:55 -0700464 def vendor_api_level(self):
465 vendor_prop = self.info_dict.get("vendor.build.prop")
466 if not vendor_prop:
467 return -1
468
469 props = [
470 "ro.board.api_level",
471 "ro.board.first_api_level",
472 "ro.product.first_api_level",
473 ]
474 for prop in props:
475 value = vendor_prop.GetProp(prop)
476 try:
477 return int(value)
478 except:
479 pass
480 return -1
481
482 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700483 def is_vabc_xor(self):
484 vendor_prop = self.info_dict.get("vendor.build.prop")
485 vabc_xor_enabled = vendor_prop and \
486 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
487 return vabc_xor_enabled
488
489 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400490 def vendor_suppressed_vabc(self):
491 vendor_prop = self.info_dict.get("vendor.build.prop")
492 vabc_suppressed = vendor_prop and \
493 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
494 return vabc_suppressed and vabc_suppressed.lower() == "true"
495
496 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700497 def oem_props(self):
498 return self._oem_props
499
500 def __getitem__(self, key):
501 return self.info_dict[key]
502
503 def __setitem__(self, key, value):
504 self.info_dict[key] = value
505
506 def get(self, key, default=None):
507 return self.info_dict.get(key, default)
508
509 def items(self):
510 return self.info_dict.items()
511
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512 def _GetRawBuildProp(self, prop, partition):
513 prop_file = '{}.build.prop'.format(
514 partition) if partition else 'build.prop'
515 partition_props = self.info_dict.get(prop_file)
516 if not partition_props:
517 return None
518 return partition_props.GetProp(prop)
519
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 def GetPartitionBuildProp(self, prop, partition):
521 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800522
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000523 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000524 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000525 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800526
Daniel Normand5fe8622020-01-08 17:01:11 -0800527 # If provided a partition for this property, only look within that
528 # partition's build.prop.
529 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800530 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800531 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800532 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000533
534 prop_val = self._GetRawBuildProp(prop, partition)
535 if prop_val is not None:
536 return prop_val
537 raise ExternalError("couldn't find %s in %s.build.prop" %
538 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800539
Tao Bao1c320f82019-10-04 23:25:12 -0700540 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800541 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700542 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
543 return self._ResolveRoProductBuildProp(prop)
544
Tianjiefdda51d2021-05-05 14:46:35 -0700545 if prop == "ro.build.id":
546 return self._GetBuildId()
547
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000548 prop_val = self._GetRawBuildProp(prop, None)
549 if prop_val is not None:
550 return prop_val
551
552 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700553
554 def _ResolveRoProductBuildProp(self, prop):
555 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000556 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700557 if prop_val:
558 return prop_val
559
Steven Laver8e2086e2020-04-27 16:26:31 -0700560 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000561 source_order_val = self._GetRawBuildProp(
562 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700563 if source_order_val:
564 source_order = source_order_val.split(",")
565 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700566 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700567
568 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700569 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700570 raise ExternalError(
571 "Invalid ro.product.property_source_order '{}'".format(source_order))
572
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000573 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700574 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000575 "ro.product", "ro.product.{}".format(source_partition), 1)
576 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700577 if prop_val:
578 return prop_val
579
580 raise ExternalError("couldn't resolve {}".format(prop))
581
Steven Laver8e2086e2020-04-27 16:26:31 -0700582 def _GetRoProductPropsDefaultSourceOrder(self):
583 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
584 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000585 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700586 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000587 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700588 if android_version == "10":
589 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
590 # NOTE: float() conversion of android_version will have rounding error.
591 # We are checking for "9" or less, and using "< 10" is well outside of
592 # possible floating point rounding.
593 try:
594 android_version_val = float(android_version)
595 except ValueError:
596 android_version_val = 0
597 if android_version_val < 10:
598 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
599 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
600
Tianjieb37c5be2020-10-15 21:27:10 -0700601 def _GetPlatformVersion(self):
602 version_sdk = self.GetBuildProp("ro.build.version.sdk")
603 # init code switches to version_release_or_codename (see b/158483506). After
604 # API finalization, release_or_codename will be the same as release. This
605 # is the best effort to support pre-S dev stage builds.
606 if int(version_sdk) >= 30:
607 try:
608 return self.GetBuildProp("ro.build.version.release_or_codename")
609 except ExternalError:
610 logger.warning('Failed to find ro.build.version.release_or_codename')
611
612 return self.GetBuildProp("ro.build.version.release")
613
Tianjiefdda51d2021-05-05 14:46:35 -0700614 def _GetBuildId(self):
615 build_id = self._GetRawBuildProp("ro.build.id", None)
616 if build_id:
617 return build_id
618
619 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
620 if not legacy_build_id:
621 raise ExternalError("Couldn't find build id in property file")
622
623 if self.use_legacy_id:
624 return legacy_build_id
625
626 # Append the top 8 chars of vbmeta digest to the existing build id. The
627 # logic needs to match the one in init, so that OTA can deliver correctly.
628 avb_enable = self.info_dict.get("avb_enable") == "true"
629 if not avb_enable:
630 raise ExternalError("AVB isn't enabled when using legacy build id")
631
632 vbmeta_digest = self.info_dict.get("vbmeta_digest")
633 if not vbmeta_digest:
634 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
635 " id")
636 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
637 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
638
639 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
640 return legacy_build_id + '.' + digest_prefix
641
Tianjieb37c5be2020-10-15 21:27:10 -0700642 def _GetPartitionPlatformVersion(self, partition):
643 try:
644 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
645 partition)
646 except ExternalError:
647 return self.GetPartitionBuildProp("ro.build.version.release",
648 partition)
649
Tao Bao1c320f82019-10-04 23:25:12 -0700650 def GetOemProperty(self, key):
651 if self.oem_props is not None and key in self.oem_props:
652 return self.oem_dicts[0][key]
653 return self.GetBuildProp(key)
654
Daniel Normand5fe8622020-01-08 17:01:11 -0800655 def GetPartitionFingerprint(self, partition):
656 return self._partition_fingerprints.get(partition, None)
657
658 def CalculatePartitionFingerprint(self, partition):
659 try:
660 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
661 except ExternalError:
662 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
663 self.GetPartitionBuildProp("ro.product.brand", partition),
664 self.GetPartitionBuildProp("ro.product.name", partition),
665 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700666 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800667 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400668 self.GetPartitionBuildProp(
669 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800670 self.GetPartitionBuildProp("ro.build.type", partition),
671 self.GetPartitionBuildProp("ro.build.tags", partition))
672
Tao Bao1c320f82019-10-04 23:25:12 -0700673 def CalculateFingerprint(self):
674 if self.oem_props is None:
675 try:
676 return self.GetBuildProp("ro.build.fingerprint")
677 except ExternalError:
678 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
679 self.GetBuildProp("ro.product.brand"),
680 self.GetBuildProp("ro.product.name"),
681 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700682 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700683 self.GetBuildProp("ro.build.id"),
684 self.GetBuildProp("ro.build.version.incremental"),
685 self.GetBuildProp("ro.build.type"),
686 self.GetBuildProp("ro.build.tags"))
687 return "%s/%s/%s:%s" % (
688 self.GetOemProperty("ro.product.brand"),
689 self.GetOemProperty("ro.product.name"),
690 self.GetOemProperty("ro.product.device"),
691 self.GetBuildProp("ro.build.thumbprint"))
692
693 def WriteMountOemScript(self, script):
694 assert self.oem_props is not None
695 recovery_mount_options = self.info_dict.get("recovery_mount_options")
696 script.Mount("/oem", recovery_mount_options)
697
698 def WriteDeviceAssertions(self, script, oem_no_mount):
699 # Read the property directly if not using OEM properties.
700 if not self.oem_props:
701 script.AssertDevice(self.device)
702 return
703
704 # Otherwise assert OEM properties.
705 if not self.oem_dicts:
706 raise ExternalError(
707 "No OEM file provided to answer expected assertions")
708
709 for prop in self.oem_props.split():
710 values = []
711 for oem_dict in self.oem_dicts:
712 if prop in oem_dict:
713 values.append(oem_dict[prop])
714 if not values:
715 raise ExternalError(
716 "The OEM file is missing the property %s" % (prop,))
717 script.AssertOemProperty(prop, values, oem_no_mount)
718
719
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000720def ReadFromInputFile(input_file, fn):
721 """Reads the contents of fn from input zipfile or directory."""
722 if isinstance(input_file, zipfile.ZipFile):
723 return input_file.read(fn).decode()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700724 elif zipfile.is_zipfile(input_file):
725 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
726 return zfp.read(fn).decode()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000727 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700728 if not os.path.isdir(input_file):
729 raise ValueError(
730 "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 +0000731 path = os.path.join(input_file, *fn.split("/"))
732 try:
733 with open(path) as f:
734 return f.read()
735 except IOError as e:
736 if e.errno == errno.ENOENT:
737 raise KeyError(fn)
738
739
Yifan Hong10482a22021-01-07 14:38:41 -0800740def ExtractFromInputFile(input_file, fn):
741 """Extracts the contents of fn from input zipfile or directory into a file."""
742 if isinstance(input_file, zipfile.ZipFile):
743 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500744 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800745 f.write(input_file.read(fn))
746 return tmp_file
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700747 elif zipfile.is_zipfile(input_file):
748 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
749 tmp_file = MakeTempFile(os.path.basename(fn))
750 with open(tmp_file, "wb") as fp:
751 fp.write(zfp.read(fn))
752 return tmp_file
Yifan Hong10482a22021-01-07 14:38:41 -0800753 else:
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700754 if not os.path.isdir(input_file):
755 raise ValueError(
756 "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 -0800757 file = os.path.join(input_file, *fn.split("/"))
758 if not os.path.exists(file):
759 raise KeyError(fn)
760 return file
761
Kelvin Zhang563750f2021-04-28 12:46:17 -0400762
jiajia tangf3f842b2021-03-17 21:49:44 +0800763class RamdiskFormat(object):
764 LZ4 = 1
765 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800766
Kelvin Zhang563750f2021-04-28 12:46:17 -0400767
TJ Rhoades6f488e92022-05-01 22:16:22 -0700768def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800769 if info_dict.get('lz4_ramdisks') == 'true':
770 ramdisk_format = RamdiskFormat.LZ4
771 else:
772 ramdisk_format = RamdiskFormat.GZ
773 return ramdisk_format
774
Kelvin Zhang563750f2021-04-28 12:46:17 -0400775
Tao Bao410ad8b2018-08-24 12:08:38 -0700776def LoadInfoDict(input_file, repacking=False):
777 """Loads the key/value pairs from the given input target_files.
778
Tianjiea85bdf02020-07-29 11:56:19 -0700779 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700780 checks and returns the parsed key/value pairs for to the given build. It's
781 usually called early when working on input target_files files, e.g. when
782 generating OTAs, or signing builds. Note that the function may be called
783 against an old target_files file (i.e. from past dessert releases). So the
784 property parsing needs to be backward compatible.
785
786 In a `META/misc_info.txt`, a few properties are stored as links to the files
787 in the PRODUCT_OUT directory. It works fine with the build system. However,
788 they are no longer available when (re)generating images from target_files zip.
789 When `repacking` is True, redirect these properties to the actual files in the
790 unzipped directory.
791
792 Args:
793 input_file: The input target_files file, which could be an open
794 zipfile.ZipFile instance, or a str for the dir that contains the files
795 unzipped from a target_files file.
796 repacking: Whether it's trying repack an target_files file after loading the
797 info dict (default: False). If so, it will rewrite a few loaded
798 properties (e.g. selinux_fc, root_dir) to point to the actual files in
799 target_files file. When doing repacking, `input_file` must be a dir.
800
801 Returns:
802 A dict that contains the parsed key/value pairs.
803
804 Raises:
805 AssertionError: On invalid input arguments.
806 ValueError: On malformed input values.
807 """
808 if repacking:
809 assert isinstance(input_file, str), \
810 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700811
Doug Zongkerc9253822014-02-04 12:17:58 -0800812 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000813 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800814
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700815 try:
Michael Runge6e836112014-04-15 17:40:21 -0700816 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700817 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700818 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700819
Tao Bao410ad8b2018-08-24 12:08:38 -0700820 if "recovery_api_version" not in d:
821 raise ValueError("Failed to find 'recovery_api_version'")
822 if "fstab_version" not in d:
823 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800824
Tao Bao410ad8b2018-08-24 12:08:38 -0700825 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700826 # "selinux_fc" properties should point to the file_contexts files
827 # (file_contexts.bin) under META/.
828 for key in d:
829 if key.endswith("selinux_fc"):
830 fc_basename = os.path.basename(d[key])
831 fc_config = os.path.join(input_file, "META", fc_basename)
832 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700833
Daniel Norman72c626f2019-05-13 15:58:14 -0700834 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700835
Tom Cherryd14b8952018-08-09 14:26:00 -0700836 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700837 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700838 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700839 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700840
David Anderson0ec64ac2019-12-06 12:21:18 -0800841 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700842 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000843 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800844 key_name = part_name + "_base_fs_file"
845 if key_name not in d:
846 continue
847 basename = os.path.basename(d[key_name])
848 base_fs_file = os.path.join(input_file, "META", basename)
849 if os.path.exists(base_fs_file):
850 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700851 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700852 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800853 "Failed to find %s base fs file: %s", part_name, base_fs_file)
854 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700855
Doug Zongker37974732010-09-16 17:44:38 -0700856 def makeint(key):
857 if key in d:
858 d[key] = int(d[key], 0)
859
860 makeint("recovery_api_version")
861 makeint("blocksize")
862 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700863 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700864 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700865 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700866 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800867 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700868
Steve Muckle903a1ca2020-05-07 17:32:10 -0700869 boot_images = "boot.img"
870 if "boot_images" in d:
871 boot_images = d["boot_images"]
872 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400873 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700874
Tao Bao765668f2019-10-04 22:03:00 -0700875 # Load recovery fstab if applicable.
876 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700877 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800878
Tianjie Xu861f4132018-09-12 11:49:33 -0700879 # Tries to load the build props for all partitions with care_map, including
880 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800881 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800882 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000883 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800884 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700885 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800886
Tao Bao3ed35d32019-10-07 20:48:48 -0700887 # Set up the salt (based on fingerprint) that will be used when adding AVB
888 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800889 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700890 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800891 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800892 fingerprint = build_info.GetPartitionFingerprint(partition)
893 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400894 d["avb_{}_salt".format(partition)] = sha256(
895 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700896
Yong Ma253b1062022-08-18 09:53:27 +0000897 # Set up the salt for partitions without build.prop
898 if build_info.fingerprint:
899 d["avb_salt"] = sha256(build_info.fingerprint.encode()).hexdigest()
900
Tianjiefdda51d2021-05-05 14:46:35 -0700901 # Set the vbmeta digest if exists
902 try:
903 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
904 except KeyError:
905 pass
906
Kelvin Zhang39aea442020-08-17 11:04:25 -0400907 try:
908 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
909 except KeyError:
910 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700911 return d
912
Tao Baod1de6f32017-03-01 16:38:48 -0800913
Daniel Norman4cc9df62019-07-18 10:11:07 -0700914def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900915 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700916 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900917
Daniel Norman4cc9df62019-07-18 10:11:07 -0700918
919def LoadDictionaryFromFile(file_path):
920 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900921 return LoadDictionaryFromLines(lines)
922
923
Michael Runge6e836112014-04-15 17:40:21 -0700924def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700925 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700926 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700927 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700928 if not line or line.startswith("#"):
929 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700930 if "=" in line:
931 name, value = line.split("=", 1)
932 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700933 return d
934
Tao Baod1de6f32017-03-01 16:38:48 -0800935
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000936class PartitionBuildProps(object):
937 """The class holds the build prop of a particular partition.
938
939 This class loads the build.prop and holds the build properties for a given
940 partition. It also partially recognizes the 'import' statement in the
941 build.prop; and calculates alternative values of some specific build
942 properties during runtime.
943
944 Attributes:
945 input_file: a zipped target-file or an unzipped target-file directory.
946 partition: name of the partition.
947 props_allow_override: a list of build properties to search for the
948 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000949 build_props: a dict of build properties for the given partition.
950 prop_overrides: a set of props that are overridden by import.
951 placeholder_values: A dict of runtime variables' values to replace the
952 placeholders in the build.prop file. We expect exactly one value for
953 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800954 ramdisk_format: If name is "boot", the format of ramdisk inside the
955 boot image. Otherwise, its value is ignored.
956 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000957 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400958
Tianjie Xu9afb2212020-05-10 21:48:15 +0000959 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000960 self.input_file = input_file
961 self.partition = name
962 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000963 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000964 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000965 self.prop_overrides = set()
966 self.placeholder_values = {}
967 if placeholder_values:
968 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000969
970 @staticmethod
971 def FromDictionary(name, build_props):
972 """Constructs an instance from a build prop dictionary."""
973
974 props = PartitionBuildProps("unknown", name)
975 props.build_props = build_props.copy()
976 return props
977
978 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800979 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000980 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800981
Devin Mooreafdd7c72021-12-13 22:04:08 +0000982 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400983 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000984 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800985 else:
986 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
987
988 props = PartitionBuildProps(input_file, name, placeholder_values)
989 props._LoadBuildProp(data)
990 return props
991
992 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000993 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800994 """
995 Read build.prop for boot image from input_file.
996 Return empty string if not found.
997 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000998 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800999 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001000 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001001 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001002 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001003 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +08001004 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001005 if prop_file is None:
1006 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -05001007 with open(prop_file, "r") as f:
1008 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -08001009
1010 @staticmethod
1011 def _ReadPartitionPropFile(input_file, name):
1012 """
1013 Read build.prop for name from input_file.
1014 Return empty string if not found.
1015 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001016 data = ''
1017 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
1018 '{}/build.prop'.format(name.upper())]:
1019 try:
1020 data = ReadFromInputFile(input_file, prop_file)
1021 break
1022 except KeyError:
1023 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -08001024 if data == '':
1025 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -08001026 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001027
Yifan Hong125d0b62020-09-24 17:07:03 -07001028 @staticmethod
1029 def FromBuildPropFile(name, build_prop_file):
1030 """Constructs an instance from a build prop file."""
1031
1032 props = PartitionBuildProps("unknown", name)
1033 with open(build_prop_file) as f:
1034 props._LoadBuildProp(f.read())
1035 return props
1036
Tianjie Xu9afb2212020-05-10 21:48:15 +00001037 def _LoadBuildProp(self, data):
1038 for line in data.split('\n'):
1039 line = line.strip()
1040 if not line or line.startswith("#"):
1041 continue
1042 if line.startswith("import"):
1043 overrides = self._ImportParser(line)
1044 duplicates = self.prop_overrides.intersection(overrides.keys())
1045 if duplicates:
1046 raise ValueError('prop {} is overridden multiple times'.format(
1047 ','.join(duplicates)))
1048 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1049 self.build_props.update(overrides)
1050 elif "=" in line:
1051 name, value = line.split("=", 1)
1052 if name in self.prop_overrides:
1053 raise ValueError('prop {} is set again after overridden by import '
1054 'statement'.format(name))
1055 self.build_props[name] = value
1056
1057 def _ImportParser(self, line):
1058 """Parses the build prop in a given import statement."""
1059
1060 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001061 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001062 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001063
1064 if len(tokens) == 3:
1065 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1066 return {}
1067
Tianjie Xu9afb2212020-05-10 21:48:15 +00001068 import_path = tokens[1]
1069 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001070 logger.warn('Unrecognized import path {}'.format(line))
1071 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001072
1073 # We only recognize a subset of import statement that the init process
1074 # supports. And we can loose the restriction based on how the dynamic
1075 # fingerprint is used in practice. The placeholder format should be
1076 # ${placeholder}, and its value should be provided by the caller through
1077 # the placeholder_values.
1078 for prop, value in self.placeholder_values.items():
1079 prop_place_holder = '${{{}}}'.format(prop)
1080 if prop_place_holder in import_path:
1081 import_path = import_path.replace(prop_place_holder, value)
1082 if '$' in import_path:
1083 logger.info('Unresolved place holder in import path %s', import_path)
1084 return {}
1085
1086 import_path = import_path.replace('/{}'.format(self.partition),
1087 self.partition.upper())
1088 logger.info('Parsing build props override from %s', import_path)
1089
1090 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1091 d = LoadDictionaryFromLines(lines)
1092 return {key: val for key, val in d.items()
1093 if key in self.props_allow_override}
1094
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001095 def __getstate__(self):
1096 state = self.__dict__.copy()
1097 # Don't pickle baz
1098 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1099 state["input_file"] = state["input_file"].filename
1100 return state
1101
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001102 def GetProp(self, prop):
1103 return self.build_props.get(prop)
1104
1105
Tianjie Xucfa86222016-03-07 16:31:19 -08001106def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1107 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001108 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001109 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001110 self.mount_point = mount_point
1111 self.fs_type = fs_type
1112 self.device = device
1113 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001114 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001115 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001116
1117 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001118 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001119 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001120 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001121 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001122
Tao Baod1de6f32017-03-01 16:38:48 -08001123 assert fstab_version == 2
1124
1125 d = {}
1126 for line in data.split("\n"):
1127 line = line.strip()
1128 if not line or line.startswith("#"):
1129 continue
1130
1131 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1132 pieces = line.split()
1133 if len(pieces) != 5:
1134 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1135
1136 # Ignore entries that are managed by vold.
1137 options = pieces[4]
1138 if "voldmanaged=" in options:
1139 continue
1140
1141 # It's a good line, parse it.
1142 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001143 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001144 options = options.split(",")
1145 for i in options:
1146 if i.startswith("length="):
1147 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001148 elif i == "slotselect":
1149 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001150 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001151 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001152 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001153
Tao Baod1de6f32017-03-01 16:38:48 -08001154 mount_flags = pieces[3]
1155 # Honor the SELinux context if present.
1156 context = None
1157 for i in mount_flags.split(","):
1158 if i.startswith("context="):
1159 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001160
Tao Baod1de6f32017-03-01 16:38:48 -08001161 mount_point = pieces[1]
1162 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001163 device=pieces[0], length=length, context=context,
1164 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001165
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001166 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001167 # system. Other areas assume system is always at "/system" so point /system
1168 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001169 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001170 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001171 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001172 return d
1173
1174
Tao Bao765668f2019-10-04 22:03:00 -07001175def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1176 """Finds the path to recovery fstab and loads its contents."""
1177 # recovery fstab is only meaningful when installing an update via recovery
1178 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001179 if info_dict.get('ab_update') == 'true' and \
1180 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001181 return None
1182
1183 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1184 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1185 # cases, since it may load the info_dict from an old build (e.g. when
1186 # generating incremental OTAs from that build).
1187 system_root_image = info_dict.get('system_root_image') == 'true'
1188 if info_dict.get('no_recovery') != 'true':
1189 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1190 if isinstance(input_file, zipfile.ZipFile):
1191 if recovery_fstab_path not in input_file.namelist():
1192 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1193 else:
1194 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1195 if not os.path.exists(path):
1196 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1197 return LoadRecoveryFSTab(
1198 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1199 system_root_image)
1200
1201 if info_dict.get('recovery_as_boot') == 'true':
1202 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1203 if isinstance(input_file, zipfile.ZipFile):
1204 if recovery_fstab_path not in input_file.namelist():
1205 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1206 else:
1207 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1208 if not os.path.exists(path):
1209 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1210 return LoadRecoveryFSTab(
1211 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1212 system_root_image)
1213
1214 return None
1215
1216
Doug Zongker37974732010-09-16 17:44:38 -07001217def DumpInfoDict(d):
1218 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001219 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001220
Dan Albert8b72aef2015-03-23 19:13:21 -07001221
Daniel Norman55417142019-11-25 16:04:36 -08001222def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001223 """Merges dynamic partition info variables.
1224
1225 Args:
1226 framework_dict: The dictionary of dynamic partition info variables from the
1227 partial framework target files.
1228 vendor_dict: The dictionary of dynamic partition info variables from the
1229 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001230
1231 Returns:
1232 The merged dynamic partition info dictionary.
1233 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001234
1235 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001236 combined = set(a.split())
1237 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001238 combined = [item.strip() for item in combined if item.strip()]
1239 return " ".join(sorted(combined))
1240
1241 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001242 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001243 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1244
1245 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001246 # For keys-value pairs that are the same, copy to merged dict
1247 for key in vendor_dict.keys():
1248 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1249 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001250
1251 merged_dict["dynamic_partition_list"] = uniq_concat(
1252 framework_dict.get("dynamic_partition_list", ""),
1253 vendor_dict.get("dynamic_partition_list", ""))
1254
1255 # Super block devices are defined by the vendor dict.
1256 if "super_block_devices" in vendor_dict:
1257 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001258 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001259 key = "super_%s_device_size" % block_device
1260 if key not in vendor_dict:
1261 raise ValueError("Vendor dict does not contain required key %s." % key)
1262 merged_dict[key] = vendor_dict[key]
1263
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001264 # Partition groups and group sizes are defined by the vendor dict because
1265 # these values may vary for each board that uses a shared system image.
1266 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001267 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001268 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001269 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001270 if key not in vendor_dict:
1271 raise ValueError("Vendor dict does not contain required key %s." % key)
1272 merged_dict[key] = vendor_dict[key]
1273
1274 # Set the partition group's partition list using a concatenation of the
1275 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001276 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001277 merged_dict[key] = uniq_concat(
1278 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301279
Daniel Normanb0c75912020-09-24 14:30:21 -07001280 # Various other flags should be copied from the vendor dict, if defined.
1281 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1282 "super_metadata_device", "super_partition_error_limit",
1283 "super_partition_size"):
1284 if key in vendor_dict.keys():
1285 merged_dict[key] = vendor_dict[key]
1286
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001287 return merged_dict
1288
1289
Daniel Norman21c34f72020-11-11 17:25:50 -08001290def PartitionMapFromTargetFiles(target_files_dir):
1291 """Builds a map from partition -> path within an extracted target files directory."""
1292 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1293 possible_subdirs = {
1294 "system": ["SYSTEM"],
1295 "vendor": ["VENDOR", "SYSTEM/vendor"],
1296 "product": ["PRODUCT", "SYSTEM/product"],
1297 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1298 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1299 "vendor_dlkm": [
1300 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1301 ],
1302 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001303 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001304 }
1305 partition_map = {}
1306 for partition, subdirs in possible_subdirs.items():
1307 for subdir in subdirs:
1308 if os.path.exists(os.path.join(target_files_dir, subdir)):
1309 partition_map[partition] = subdir
1310 break
1311 return partition_map
1312
1313
Daniel Normand3351562020-10-29 12:33:11 -07001314def SharedUidPartitionViolations(uid_dict, partition_groups):
1315 """Checks for APK sharedUserIds that cross partition group boundaries.
1316
1317 This uses a single or merged build's shareduid_violation_modules.json
1318 output file, as generated by find_shareduid_violation.py or
1319 core/tasks/find-shareduid-violation.mk.
1320
1321 An error is defined as a sharedUserId that is found in a set of partitions
1322 that span more than one partition group.
1323
1324 Args:
1325 uid_dict: A dictionary created by using the standard json module to read a
1326 complete shareduid_violation_modules.json file.
1327 partition_groups: A list of groups, where each group is a list of
1328 partitions.
1329
1330 Returns:
1331 A list of error messages.
1332 """
1333 errors = []
1334 for uid, partitions in uid_dict.items():
1335 found_in_groups = [
1336 group for group in partition_groups
1337 if set(partitions.keys()) & set(group)
1338 ]
1339 if len(found_in_groups) > 1:
1340 errors.append(
1341 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1342 % (uid, ",".join(sorted(partitions.keys()))))
1343 return errors
1344
1345
Daniel Norman21c34f72020-11-11 17:25:50 -08001346def RunHostInitVerifier(product_out, partition_map):
1347 """Runs host_init_verifier on the init rc files within partitions.
1348
1349 host_init_verifier searches the etc/init path within each partition.
1350
1351 Args:
1352 product_out: PRODUCT_OUT directory, containing partition directories.
1353 partition_map: A map of partition name -> relative path within product_out.
1354 """
1355 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1356 cmd = ["host_init_verifier"]
1357 for partition, path in partition_map.items():
1358 if partition not in allowed_partitions:
1359 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1360 partition)
1361 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1362 # Add --property-contexts if the file exists on the partition.
1363 property_contexts = "%s_property_contexts" % (
1364 "plat" if partition == "system" else partition)
1365 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1366 property_contexts)
1367 if os.path.exists(property_contexts_path):
1368 cmd.append("--property-contexts=%s" % property_contexts_path)
1369 # Add the passwd file if the file exists on the partition.
1370 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1371 if os.path.exists(passwd_path):
1372 cmd.extend(["-p", passwd_path])
1373 return RunAndCheckOutput(cmd)
1374
1375
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001376def AppendAVBSigningArgs(cmd, partition):
1377 """Append signing arguments for avbtool."""
1378 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
zhangyongpeng70756972023-04-12 15:31:33 +08001379 key_path = ResolveAVBSigningPathArgs(OPTIONS.info_dict.get("avb_" + partition + "_key_path"))
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001380 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1381 if key_path and algorithm:
1382 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001383 avb_salt = OPTIONS.info_dict.get("avb_salt")
1384 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001385 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001386 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001387
1388
zhangyongpeng70756972023-04-12 15:31:33 +08001389def ResolveAVBSigningPathArgs(split_args):
1390
1391 def ResolveBinaryPath(path):
1392 if os.path.exists(path):
1393 return path
1394 new_path = os.path.join(OPTIONS.search_path, path)
1395 if os.path.exists(new_path):
1396 return new_path
1397 raise ExternalError(
1398 "Failed to find {}".format(new_path))
1399
1400 if not split_args:
1401 return split_args
1402
1403 if isinstance(split_args, list):
1404 for index, arg in enumerate(split_args[:-1]):
1405 if arg == '--signing_helper':
1406 signing_helper_path = split_args[index + 1]
1407 split_args[index + 1] = ResolveBinaryPath(signing_helper_path)
1408 break
1409 elif isinstance(split_args, str):
1410 split_args = ResolveBinaryPath(split_args)
1411
1412 return split_args
1413
1414
Tao Bao765668f2019-10-04 22:03:00 -07001415def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001416 """Returns the VBMeta arguments for partition.
1417
1418 It sets up the VBMeta argument by including the partition descriptor from the
1419 given 'image', or by configuring the partition as a chained partition.
1420
1421 Args:
1422 partition: The name of the partition (e.g. "system").
1423 image: The path to the partition image.
1424 info_dict: A dict returned by common.LoadInfoDict(). Will use
1425 OPTIONS.info_dict if None has been given.
1426
1427 Returns:
1428 A list of VBMeta arguments.
1429 """
1430 if info_dict is None:
1431 info_dict = OPTIONS.info_dict
1432
1433 # Check if chain partition is used.
1434 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001435 if not key_path:
1436 return ["--include_descriptors_from_image", image]
1437
1438 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1439 # into vbmeta.img. The recovery image will be configured on an independent
1440 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1441 # See details at
1442 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001443 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001444 return []
1445
1446 # Otherwise chain the partition into vbmeta.
1447 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1448 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001449
1450
Tao Bao02a08592018-07-22 12:40:45 -07001451def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1452 """Constructs and returns the arg to build or verify a chained partition.
1453
1454 Args:
1455 partition: The partition name.
1456 info_dict: The info dict to look up the key info and rollback index
1457 location.
1458 key: The key to be used for building or verifying the partition. Defaults to
1459 the key listed in info_dict.
1460
1461 Returns:
1462 A string of form "partition:rollback_index_location:key" that can be used to
1463 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001464 """
1465 if key is None:
1466 key = info_dict["avb_" + partition + "_key_path"]
zhangyongpeng70756972023-04-12 15:31:33 +08001467 key = ResolveAVBSigningPathArgs(key)
Tao Bao1ac886e2019-06-26 11:58:22 -07001468 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001469 rollback_index_location = info_dict[
1470 "avb_" + partition + "_rollback_index_location"]
1471 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1472
1473
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001474def _HasGkiCertificationArgs():
1475 return ("gki_signing_key_path" in OPTIONS.info_dict and
1476 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001477
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001478
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001479def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001480 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001481 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001482
zhangyongpeng70756972023-04-12 15:31:33 +08001483 key_path = ResolveAVBSigningPathArgs(key_path)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001484
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001485 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001486 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001487 raise ExternalError(
1488 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001489
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001490 output_certificate = tempfile.NamedTemporaryFile()
1491 cmd = [
1492 "generate_gki_certificate",
1493 "--name", image_name,
1494 "--algorithm", algorithm,
1495 "--key", key_path,
1496 "--output", output_certificate.name,
1497 image,
1498 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001499
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001500 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1501 signature_args = signature_args.strip()
1502 if signature_args:
1503 cmd.extend(["--additional_avb_args", signature_args])
1504
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001505 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001506 args = args.strip()
1507 if args:
1508 cmd.extend(["--additional_avb_args", args])
1509
1510 RunAndCheckOutput(cmd)
1511
1512 output_certificate.seek(os.SEEK_SET, 0)
1513 data = output_certificate.read()
1514 output_certificate.close()
1515 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001516
1517
Daniel Norman276f0622019-07-26 14:13:51 -07001518def BuildVBMeta(image_path, partitions, name, needed_partitions):
1519 """Creates a VBMeta image.
1520
1521 It generates the requested VBMeta image. The requested image could be for
1522 top-level or chained VBMeta image, which is determined based on the name.
1523
1524 Args:
1525 image_path: The output path for the new VBMeta image.
1526 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001527 values. Only valid partition names are accepted, as partitions listed
1528 in common.AVB_PARTITIONS and custom partitions listed in
1529 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001530 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1531 needed_partitions: Partitions whose descriptors should be included into the
1532 generated VBMeta image.
1533
1534 Raises:
1535 AssertionError: On invalid input args.
1536 """
1537 avbtool = OPTIONS.info_dict["avb_avbtool"]
1538 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1539 AppendAVBSigningArgs(cmd, name)
1540
Hongguang Chenf23364d2020-04-27 18:36:36 -07001541 custom_partitions = OPTIONS.info_dict.get(
1542 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001543 custom_avb_partitions = ["vbmeta_" + part for part in OPTIONS.info_dict.get("avb_custom_vbmeta_images_partition_list", "").strip().split()]
Hongguang Chenf23364d2020-04-27 18:36:36 -07001544
Daniel Norman276f0622019-07-26 14:13:51 -07001545 for partition, path in partitions.items():
1546 if partition not in needed_partitions:
1547 continue
1548 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001549 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001550 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001551 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001552 'Unknown partition: {}'.format(partition)
1553 assert os.path.exists(path), \
1554 'Failed to find {} for {}'.format(path, partition)
1555 cmd.extend(GetAvbPartitionArg(partition, path))
1556
1557 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1558 if args and args.strip():
1559 split_args = shlex.split(args)
1560 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001561 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001562 # as a path relative to source tree, which may not be available at the
1563 # same location when running this script (we have the input target_files
1564 # zip only). For such cases, we additionally scan other locations (e.g.
1565 # IMAGES/, RADIO/, etc) before bailing out.
1566 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001567 chained_image = split_args[index + 1]
1568 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001569 continue
1570 found = False
1571 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1572 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001573 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001574 if os.path.exists(alt_path):
1575 split_args[index + 1] = alt_path
1576 found = True
1577 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001578 assert found, 'Failed to find {}'.format(chained_image)
zhangyongpeng70756972023-04-12 15:31:33 +08001579
1580 split_args = ResolveAVBSigningPathArgs(split_args)
Daniel Norman276f0622019-07-26 14:13:51 -07001581 cmd.extend(split_args)
1582
1583 RunAndCheckOutput(cmd)
1584
1585
jiajia tang836f76b2021-04-02 14:48:26 +08001586def _MakeRamdisk(sourcedir, fs_config_file=None,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001587 dev_node_file=None,
jiajia tang836f76b2021-04-02 14:48:26 +08001588 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001589 ramdisk_img = tempfile.NamedTemporaryFile()
1590
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001591 cmd = ["mkbootfs"]
1592
1593 if fs_config_file and os.access(fs_config_file, os.F_OK):
1594 cmd.extend(["-f", fs_config_file])
1595
1596 if dev_node_file and os.access(dev_node_file, os.F_OK):
1597 cmd.extend(["-n", dev_node_file])
1598
1599 cmd.append(os.path.join(sourcedir, "RAMDISK"))
1600
Steve Mucklee1b10862019-07-10 10:49:37 -07001601 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001602 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001603 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001604 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001605 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001606 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001607 else:
1608 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001609
1610 p2.wait()
1611 p1.wait()
1612 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001613 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001614
1615 return ramdisk_img
1616
1617
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001618def _BuildBootableImage(image_name, sourcedir, fs_config_file,
1619 dev_node_file=None, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001620 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001621 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001622
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001623 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001624 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1625 we are building a two-step special image (i.e. building a recovery image to
1626 be loaded into /boot in two-step OTAs).
1627
1628 Return the image data, or None if sourcedir does not appear to contains files
1629 for building the requested image.
1630 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001631
Yifan Hong63c5ca12020-10-08 11:54:02 -07001632 if info_dict is None:
1633 info_dict = OPTIONS.info_dict
1634
Steve Muckle9793cf62020-04-08 18:27:00 -07001635 # "boot" or "recovery", without extension.
1636 partition_name = os.path.basename(sourcedir).lower()
1637
Yifan Hong63c5ca12020-10-08 11:54:02 -07001638 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001639 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001640 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1641 logger.info("Excluded kernel binary from recovery image.")
1642 else:
1643 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001644 elif partition_name == "init_boot":
1645 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001646 else:
1647 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001648 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001649 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001650 return None
1651
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001652 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1653
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001654 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001655 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001656
Doug Zongkereef39442009-04-02 12:14:19 -07001657 img = tempfile.NamedTemporaryFile()
1658
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001659 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001660 ramdisk_format = GetRamdiskFormat(info_dict)
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001661 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, dev_node_file,
jiajia tang836f76b2021-04-02 14:48:26 +08001662 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001663
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001664 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1665 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1666
Yifan Hong63c5ca12020-10-08 11:54:02 -07001667 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001668 if kernel_path is not None:
1669 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001670
Benoit Fradina45a8682014-07-14 21:00:43 +02001671 fn = os.path.join(sourcedir, "second")
1672 if os.access(fn, os.F_OK):
1673 cmd.append("--second")
1674 cmd.append(fn)
1675
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001676 fn = os.path.join(sourcedir, "dtb")
1677 if os.access(fn, os.F_OK):
1678 cmd.append("--dtb")
1679 cmd.append(fn)
1680
Doug Zongker171f1cd2009-06-15 22:36:37 -07001681 fn = os.path.join(sourcedir, "cmdline")
1682 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001683 cmd.append("--cmdline")
1684 cmd.append(open(fn).read().rstrip("\n"))
1685
1686 fn = os.path.join(sourcedir, "base")
1687 if os.access(fn, os.F_OK):
1688 cmd.append("--base")
1689 cmd.append(open(fn).read().rstrip("\n"))
1690
Ying Wang4de6b5b2010-08-25 14:29:34 -07001691 fn = os.path.join(sourcedir, "pagesize")
1692 if os.access(fn, os.F_OK):
1693 cmd.append("--pagesize")
1694 cmd.append(open(fn).read().rstrip("\n"))
1695
Steve Mucklef84668e2020-03-16 19:13:46 -07001696 if partition_name == "recovery":
1697 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301698 if not args:
1699 # Fall back to "mkbootimg_args" for recovery image
1700 # in case "recovery_mkbootimg_args" is not set.
1701 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001702 elif partition_name == "init_boot":
1703 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001704 else:
1705 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001706 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001707 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001708
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001709 args = info_dict.get("mkbootimg_version_args")
1710 if args and args.strip():
1711 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001712
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001713 if has_ramdisk:
1714 cmd.extend(["--ramdisk", ramdisk_img.name])
1715
Tao Baod95e9fd2015-03-29 23:07:41 -07001716 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001717 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001718 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001719 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001720 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001721 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001722
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001723 if partition_name == "recovery":
1724 if info_dict.get("include_recovery_dtbo") == "true":
1725 fn = os.path.join(sourcedir, "recovery_dtbo")
1726 cmd.extend(["--recovery_dtbo", fn])
1727 if info_dict.get("include_recovery_acpio") == "true":
1728 fn = os.path.join(sourcedir, "recovery_acpio")
1729 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001730
Tao Bao986ee862018-10-04 15:46:16 -07001731 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001732
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001733 if _HasGkiCertificationArgs():
1734 if not os.path.exists(img.name):
1735 raise ValueError("Cannot find GKI boot.img")
1736 if kernel_path is None or not os.path.exists(kernel_path):
1737 raise ValueError("Cannot find GKI kernel.img")
1738
1739 # Certify GKI images.
1740 boot_signature_bytes = b''
1741 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1742 boot_signature_bytes += _GenerateGkiCertificate(
1743 kernel_path, "generic_kernel")
1744
1745 BOOT_SIGNATURE_SIZE = 16 * 1024
1746 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1747 raise ValueError(
1748 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1749 boot_signature_bytes += (
1750 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1751 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1752
1753 with open(img.name, 'ab') as f:
1754 f.write(boot_signature_bytes)
1755
Tao Baod95e9fd2015-03-29 23:07:41 -07001756 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001757 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001758 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001759 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001760 # We have switched from the prebuilt futility binary to using the tool
1761 # (futility-host) built from the source. Override the setting in the old
1762 # TF.zip.
1763 futility = info_dict["futility"]
1764 if futility.startswith("prebuilts/"):
1765 futility = "futility-host"
1766 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001767 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001768 info_dict["vboot_key"] + ".vbprivk",
1769 info_dict["vboot_subkey"] + ".vbprivk",
1770 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001771 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001772 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001773
Tao Baof3282b42015-04-01 11:21:55 -07001774 # Clean up the temp files.
1775 img_unsigned.close()
1776 img_keyblock.close()
1777
David Zeuthen8fecb282017-12-01 16:24:01 -05001778 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001779 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001780 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001781 if partition_name == "recovery":
1782 part_size = info_dict["recovery_size"]
1783 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001784 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001785 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001786 "--partition_size", str(part_size), "--partition_name",
1787 partition_name]
1788 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001789 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001790 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001791 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1792 cmd.extend(split_args)
Tao Bao986ee862018-10-04 15:46:16 -07001793 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001794
1795 img.seek(os.SEEK_SET, 0)
1796 data = img.read()
1797
1798 if has_ramdisk:
1799 ramdisk_img.close()
1800 img.close()
1801
1802 return data
1803
1804
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001805def _SignBootableImage(image_path, prebuilt_name, partition_name,
1806 info_dict=None):
1807 """Performs AVB signing for a prebuilt boot.img.
1808
1809 Args:
1810 image_path: The full path of the image, e.g., /path/to/boot.img.
1811 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001812 boot-5.10.img, recovery.img or init_boot.img.
1813 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001814 info_dict: The information dict read from misc_info.txt.
1815 """
1816 if info_dict is None:
1817 info_dict = OPTIONS.info_dict
1818
1819 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1820 if info_dict.get("avb_enable") == "true":
1821 avbtool = info_dict["avb_avbtool"]
1822 if partition_name == "recovery":
1823 part_size = info_dict["recovery_size"]
1824 else:
1825 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1826
1827 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1828 "--partition_size", str(part_size), "--partition_name",
1829 partition_name]
1830 AppendAVBSigningArgs(cmd, partition_name)
1831 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1832 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001833 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1834 cmd.extend(split_args)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001835 RunAndCheckOutput(cmd)
1836
1837
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001838def HasRamdisk(partition_name, info_dict=None):
1839 """Returns true/false to see if a bootable image should have a ramdisk.
1840
1841 Args:
1842 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1843 info_dict: The information dict read from misc_info.txt.
1844 """
1845 if info_dict is None:
1846 info_dict = OPTIONS.info_dict
1847
1848 if partition_name != "boot":
1849 return True # init_boot.img or recovery.img has a ramdisk.
1850
1851 if info_dict.get("recovery_as_boot") == "true":
1852 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1853
Bowgo Tsai85578e02022-04-19 10:50:59 +08001854 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1855 return False # A GKI boot.img has no ramdisk since Android-13.
1856
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001857 if info_dict.get("system_root_image") == "true":
1858 # The ramdisk content is merged into the system.img, so there is NO
1859 # ramdisk in the boot.img or boot-<kernel version>.img.
1860 return False
1861
1862 if info_dict.get("init_boot") == "true":
1863 # The ramdisk is moved to the init_boot.img, so there is NO
1864 # ramdisk in the boot.img or boot-<kernel version>.img.
1865 return False
1866
1867 return True
1868
1869
Doug Zongkerd5131602012-08-02 14:46:42 -07001870def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001871 info_dict=None, two_step_image=False,
1872 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001873 """Return a File object with the desired bootable image.
1874
1875 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1876 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1877 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001878
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001879 if info_dict is None:
1880 info_dict = OPTIONS.info_dict
1881
Doug Zongker55d93282011-01-25 17:03:34 -08001882 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1883 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001884 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001885 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001886
1887 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1888 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001889 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001890 return File.FromLocalFile(name, prebuilt_path)
1891
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001892 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001893 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1894 if os.path.exists(prebuilt_path):
1895 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1896 signed_img = MakeTempFile()
1897 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001898 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1899 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001900
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001901 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001902
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001903 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001904
Doug Zongker6f1d0312014-08-22 08:07:12 -07001905 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001906 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001907 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001908 os.path.join(unpack_dir, 'META/ramdisk_node_list')
1909 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001910 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001911 if data:
1912 return File(name, data)
1913 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001914
Doug Zongkereef39442009-04-02 12:14:19 -07001915
Lucas Wei03230252022-04-18 16:00:40 +08001916def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001917 """Build a vendor boot image from the specified sourcedir.
1918
1919 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1920 turn them into a vendor boot image.
1921
1922 Return the image data, or None if sourcedir does not appear to contains files
1923 for building the requested image.
1924 """
1925
1926 if info_dict is None:
1927 info_dict = OPTIONS.info_dict
1928
1929 img = tempfile.NamedTemporaryFile()
1930
TJ Rhoades6f488e92022-05-01 22:16:22 -07001931 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001932 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001933
1934 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1935 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1936
1937 cmd = [mkbootimg]
1938
1939 fn = os.path.join(sourcedir, "dtb")
1940 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001941 has_vendor_kernel_boot = (info_dict.get(
1942 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001943
1944 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1945 # Otherwise pack dtb into vendor_boot.
1946 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1947 cmd.append("--dtb")
1948 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001949
1950 fn = os.path.join(sourcedir, "vendor_cmdline")
1951 if os.access(fn, os.F_OK):
1952 cmd.append("--vendor_cmdline")
1953 cmd.append(open(fn).read().rstrip("\n"))
1954
1955 fn = os.path.join(sourcedir, "base")
1956 if os.access(fn, os.F_OK):
1957 cmd.append("--base")
1958 cmd.append(open(fn).read().rstrip("\n"))
1959
1960 fn = os.path.join(sourcedir, "pagesize")
1961 if os.access(fn, os.F_OK):
1962 cmd.append("--pagesize")
1963 cmd.append(open(fn).read().rstrip("\n"))
1964
1965 args = info_dict.get("mkbootimg_args")
1966 if args and args.strip():
1967 cmd.extend(shlex.split(args))
1968
1969 args = info_dict.get("mkbootimg_version_args")
1970 if args and args.strip():
1971 cmd.extend(shlex.split(args))
1972
1973 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1974 cmd.extend(["--vendor_boot", img.name])
1975
Devin Moore50509012021-01-13 10:45:04 -08001976 fn = os.path.join(sourcedir, "vendor_bootconfig")
1977 if os.access(fn, os.F_OK):
1978 cmd.append("--vendor_bootconfig")
1979 cmd.append(fn)
1980
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001981 ramdisk_fragment_imgs = []
1982 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1983 if os.access(fn, os.F_OK):
1984 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1985 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001986 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1987 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001988 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001989 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1990 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001991 # Use prebuilt image if found, else create ramdisk from supplied files.
1992 if os.access(fn, os.F_OK):
1993 ramdisk_fragment_pathname = fn
1994 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001995 ramdisk_fragment_root = os.path.join(
1996 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001997 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1998 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001999 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
2000 ramdisk_fragment_pathname = ramdisk_fragment_img.name
2001 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
2002
Steve Mucklee1b10862019-07-10 10:49:37 -07002003 RunAndCheckOutput(cmd)
2004
2005 # AVB: if enabled, calculate and add hash.
2006 if info_dict.get("avb_enable") == "true":
2007 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08002008 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07002009 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08002010 "--partition_size", str(part_size), "--partition_name", partition_name]
2011 AppendAVBSigningArgs(cmd, partition_name)
2012 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07002013 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08002014 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
2015 cmd.extend(split_args)
Steve Mucklee1b10862019-07-10 10:49:37 -07002016 RunAndCheckOutput(cmd)
2017
2018 img.seek(os.SEEK_SET, 0)
2019 data = img.read()
2020
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002021 for f in ramdisk_fragment_imgs:
2022 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002023 ramdisk_img.close()
2024 img.close()
2025
2026 return data
2027
2028
2029def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2030 info_dict=None):
2031 """Return a File object with the desired vendor boot image.
2032
2033 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2034 the source files in 'unpack_dir'/'tree_subdir'."""
2035
2036 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2037 if os.path.exists(prebuilt_path):
2038 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2039 return File.FromLocalFile(name, prebuilt_path)
2040
2041 logger.info("building image from target_files %s...", tree_subdir)
2042
2043 if info_dict is None:
2044 info_dict = OPTIONS.info_dict
2045
Kelvin Zhang0876c412020-06-23 15:06:58 -04002046 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002047 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2048 if data:
2049 return File(name, data)
2050 return None
2051
2052
2053def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002054 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002055 """Return a File object with the desired vendor kernel boot image.
2056
2057 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2058 the source files in 'unpack_dir'/'tree_subdir'."""
2059
2060 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2061 if os.path.exists(prebuilt_path):
2062 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2063 return File.FromLocalFile(name, prebuilt_path)
2064
2065 logger.info("building image from target_files %s...", tree_subdir)
2066
2067 if info_dict is None:
2068 info_dict = OPTIONS.info_dict
2069
2070 data = _BuildVendorBootImage(
2071 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002072 if data:
2073 return File(name, data)
2074 return None
2075
2076
Narayan Kamatha07bf042017-08-14 14:49:21 +01002077def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002078 """Gunzips the given gzip compressed file to a given output file."""
2079 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002080 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002081 shutil.copyfileobj(in_file, out_file)
2082
2083
Tao Bao0ff15de2019-03-20 11:26:06 -07002084def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002085 """Unzips the archive to the given directory.
2086
2087 Args:
2088 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002089 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002090 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2091 archvie. Non-matching patterns will be filtered out. If there's no match
2092 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002093 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002094 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002095 if patterns is not None:
2096 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002097 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002098 names = input_zip.namelist()
2099 filtered = [
2100 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2101
2102 # There isn't any matching files. Don't unzip anything.
2103 if not filtered:
2104 return
2105 cmd.extend(filtered)
2106
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002107 RunAndCheckOutput(cmd)
2108
2109
Daniel Norman78554ea2021-09-14 10:29:38 -07002110def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002111 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002112
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002113 Args:
2114 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2115 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2116
Daniel Norman78554ea2021-09-14 10:29:38 -07002117 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002118 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002119
Tao Bao1c830bf2017-12-25 10:43:47 -08002120 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002121 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002122 """
Doug Zongkereef39442009-04-02 12:14:19 -07002123
Tao Bao1c830bf2017-12-25 10:43:47 -08002124 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002125 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2126 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002127 UnzipToDir(m.group(1), tmp, patterns)
2128 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002129 filename = m.group(1)
2130 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002131 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002132
Tao Baodba59ee2018-01-09 13:21:02 -08002133 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002134
2135
Yifan Hong8a66a712019-04-04 15:37:57 -07002136def GetUserImage(which, tmpdir, input_zip,
2137 info_dict=None,
2138 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002139 reset_file_map=False):
2140 """Returns an Image object suitable for passing to BlockImageDiff.
2141
2142 This function loads the specified image from the given path. If the specified
2143 image is sparse, it also performs additional processing for OTA purpose. For
2144 example, it always adds block 0 to clobbered blocks list. It also detects
2145 files that cannot be reconstructed from the block list, for whom we should
2146 avoid applying imgdiff.
2147
2148 Args:
2149 which: The partition name.
2150 tmpdir: The directory that contains the prebuilt image and block map file.
2151 input_zip: The target-files ZIP archive.
2152 info_dict: The dict to be looked up for relevant info.
2153 allow_shared_blocks: If image is sparse, whether having shared blocks is
2154 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002155 reset_file_map: If true and image is sparse, reset file map before returning
2156 the image.
2157 Returns:
2158 A Image object. If it is a sparse image and reset_file_map is False, the
2159 image will have file_map info loaded.
2160 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002161 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002162 info_dict = LoadInfoDict(input_zip)
2163
Kelvin Zhang04521282023-03-02 09:42:52 -08002164 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002165
2166 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2167 # shared blocks (i.e. some blocks will show up in multiple files' block
2168 # list). We can only allocate such shared blocks to the first "owner", and
2169 # disable imgdiff for all later occurrences.
2170 if allow_shared_blocks is None:
2171 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2172
2173 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002174 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002175 if reset_file_map:
2176 img.ResetFileMap()
2177 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002178 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002179
2180
hungweichencc9c05d2022-08-23 05:45:42 +00002181def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002182 """Returns a Image object suitable for passing to BlockImageDiff.
2183
2184 This function loads the specified non-sparse image from the given path.
2185
2186 Args:
2187 which: The partition name.
2188 tmpdir: The directory that contains the prebuilt image and block map file.
2189 Returns:
2190 A Image object.
2191 """
2192 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2193 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2194
2195 # The image and map files must have been created prior to calling
2196 # ota_from_target_files.py (since LMP).
2197 assert os.path.exists(path) and os.path.exists(mappath)
2198
hungweichencc9c05d2022-08-23 05:45:42 +00002199 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002200
Yifan Hong8a66a712019-04-04 15:37:57 -07002201
hungweichencc9c05d2022-08-23 05:45:42 +00002202def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002203 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2204
2205 This function loads the specified sparse image from the given path, and
2206 performs additional processing for OTA purpose. For example, it always adds
2207 block 0 to clobbered blocks list. It also detects files that cannot be
2208 reconstructed from the block list, for whom we should avoid applying imgdiff.
2209
2210 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002211 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002212 tmpdir: The directory that contains the prebuilt image and block map file.
2213 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002214 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002215 Returns:
2216 A SparseImage object, with file_map info loaded.
2217 """
Tao Baoc765cca2018-01-31 17:32:40 -08002218 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2219 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2220
2221 # The image and map files must have been created prior to calling
2222 # ota_from_target_files.py (since LMP).
2223 assert os.path.exists(path) and os.path.exists(mappath)
2224
2225 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2226 # it to clobbered_blocks so that it will be written to the target
2227 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2228 clobbered_blocks = "0"
2229
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002230 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002231 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002232
2233 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2234 # if they contain all zeros. We can't reconstruct such a file from its block
2235 # list. Tag such entries accordingly. (Bug: 65213616)
2236 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002237 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002238 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002239 continue
2240
Tom Cherryd14b8952018-08-09 14:26:00 -07002241 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2242 # filename listed in system.map may contain an additional leading slash
2243 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2244 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002245 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002246 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002247 arcname = entry.lstrip('/')
2248 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002249 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002250 else:
2251 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002252
2253 assert arcname in input_zip.namelist(), \
2254 "Failed to find the ZIP entry for {}".format(entry)
2255
Tao Baoc765cca2018-01-31 17:32:40 -08002256 info = input_zip.getinfo(arcname)
2257 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002258
2259 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002260 # image, check the original block list to determine its completeness. Note
2261 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002262 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002263 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002264
Tao Baoc765cca2018-01-31 17:32:40 -08002265 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2266 ranges.extra['incomplete'] = True
2267
2268 return image
2269
2270
Doug Zongkereef39442009-04-02 12:14:19 -07002271def GetKeyPasswords(keylist):
2272 """Given a list of keys, prompt the user to enter passwords for
2273 those which require them. Return a {key: password} dict. password
2274 will be None if the key has no password."""
2275
Doug Zongker8ce7c252009-05-22 13:34:54 -07002276 no_passwords = []
2277 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002278 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002279 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002280
2281 # sorted() can't compare strings to None, so convert Nones to strings
2282 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002283 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002284 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002285 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002286 continue
2287
T.R. Fullhart37e10522013-03-18 10:31:26 -07002288 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002289 "-inform", "DER", "-nocrypt"],
2290 stdin=devnull.fileno(),
2291 stdout=devnull.fileno(),
2292 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002293 p.communicate()
2294 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002295 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002296 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002297 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002298 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2299 "-inform", "DER", "-passin", "pass:"],
2300 stdin=devnull.fileno(),
2301 stdout=devnull.fileno(),
2302 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002303 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002304 if p.returncode == 0:
2305 # Encrypted key with empty string as password.
2306 key_passwords[k] = ''
2307 elif stderr.startswith('Error decrypting key'):
2308 # Definitely encrypted key.
2309 # It would have said "Error reading key" if it didn't parse correctly.
2310 need_passwords.append(k)
2311 else:
2312 # Potentially, a type of key that openssl doesn't understand.
2313 # We'll let the routines in signapk.jar handle it.
2314 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002315 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002316
T.R. Fullhart37e10522013-03-18 10:31:26 -07002317 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002318 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002319 return key_passwords
2320
2321
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002322def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002323 """Gets the minSdkVersion declared in the APK.
2324
Martin Stjernholm58472e82022-01-07 22:08:47 +00002325 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2326 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002327
2328 Args:
2329 apk_name: The APK filename.
2330
2331 Returns:
2332 The parsed SDK version string.
2333
2334 Raises:
2335 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002336 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002337 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002338 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002339 stderr=subprocess.PIPE)
2340 stdoutdata, stderrdata = proc.communicate()
2341 if proc.returncode != 0:
2342 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002343 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2344 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002345
Tao Baof47bf0f2018-03-21 23:28:51 -07002346 for line in stdoutdata.split("\n"):
2347 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002348 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2349 if m:
2350 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002351 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002352
2353
2354def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002355 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002356
Tao Baof47bf0f2018-03-21 23:28:51 -07002357 If minSdkVersion is set to a codename, it is translated to a number using the
2358 provided map.
2359
2360 Args:
2361 apk_name: The APK filename.
2362
2363 Returns:
2364 The parsed SDK version number.
2365
2366 Raises:
2367 ExternalError: On failing to get the min SDK version number.
2368 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002369 version = GetMinSdkVersion(apk_name)
2370 try:
2371 return int(version)
2372 except ValueError:
2373 # Not a decimal number. Codename?
2374 if version in codename_to_api_level_map:
2375 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002376 raise ExternalError(
2377 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2378 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002379
2380
2381def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002382 codename_to_api_level_map=None, whole_file=False,
2383 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002384 """Sign the input_name zip/jar/apk, producing output_name. Use the
2385 given key and password (the latter may be None if the key does not
2386 have a password.
2387
Doug Zongker951495f2009-08-14 12:44:19 -07002388 If whole_file is true, use the "-w" option to SignApk to embed a
2389 signature that covers the whole file in the archive comment of the
2390 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002391
2392 min_api_level is the API Level (int) of the oldest platform this file may end
2393 up on. If not specified for an APK, the API Level is obtained by interpreting
2394 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2395
2396 codename_to_api_level_map is needed to translate the codename which may be
2397 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002398
2399 Caller may optionally specify extra args to be passed to SignApk, which
2400 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002401 """
Tao Bao76def242017-11-21 09:25:31 -08002402 if codename_to_api_level_map is None:
2403 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002404 if extra_signapk_args is None:
2405 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002406
Alex Klyubin9667b182015-12-10 13:38:50 -08002407 java_library_path = os.path.join(
2408 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2409
Tao Baoe95540e2016-11-08 12:08:53 -08002410 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2411 ["-Djava.library.path=" + java_library_path,
2412 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002413 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002414 if whole_file:
2415 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002416
2417 min_sdk_version = min_api_level
2418 if min_sdk_version is None:
2419 if not whole_file:
2420 min_sdk_version = GetMinSdkVersionInt(
2421 input_name, codename_to_api_level_map)
2422 if min_sdk_version is not None:
2423 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2424
T.R. Fullhart37e10522013-03-18 10:31:26 -07002425 cmd.extend([key + OPTIONS.public_key_suffix,
2426 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002427 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002428
Tao Bao73dd4f42018-10-04 16:25:33 -07002429 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002430 if password is not None:
2431 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002432 stdoutdata, _ = proc.communicate(password)
2433 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002434 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002435 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002436 proc.returncode, stdoutdata))
2437
Doug Zongkereef39442009-04-02 12:14:19 -07002438
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002439def SignSePolicy(sepolicy, key, password):
2440 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2441 an RSA .sig signature files.
2442 """
2443
2444 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002445 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002446 return False
2447
2448 java_library_path = os.path.join(
2449 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2450
2451 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002452 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002453 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002454 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002455
2456 cmd.extend([key + OPTIONS.public_key_suffix,
2457 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002458 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002459
2460 proc = Run(cmd, stdin=subprocess.PIPE)
2461 if password is not None:
2462 password += "\n"
2463 stdoutdata, _ = proc.communicate(password)
2464 if proc.returncode != 0:
2465 raise ExternalError(
2466 "Failed to run sign sepolicy: return code {}:\n{}".format(
2467 proc.returncode, stdoutdata))
2468 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002469
Kelvin Zhangf294c872022-10-06 14:21:36 -07002470
Doug Zongker37974732010-09-16 17:44:38 -07002471def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002472 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002473
Tao Bao9dd909e2017-11-14 11:27:32 -08002474 For non-AVB images, raise exception if the data is too big. Print a warning
2475 if the data is nearing the maximum size.
2476
2477 For AVB images, the actual image size should be identical to the limit.
2478
2479 Args:
2480 data: A string that contains all the data for the partition.
2481 target: The partition name. The ".img" suffix is optional.
2482 info_dict: The dict to be looked up for relevant info.
2483 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002484 if target.endswith(".img"):
2485 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002486 mount_point = "/" + target
2487
Ying Wangf8824af2014-06-03 14:07:27 -07002488 fs_type = None
2489 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002490 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002491 if mount_point == "/userdata":
2492 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002493 p = info_dict["fstab"][mount_point]
2494 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002495 device = p.device
2496 if "/" in device:
2497 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002498 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002499 if not fs_type or not limit:
2500 return
Doug Zongkereef39442009-04-02 12:14:19 -07002501
Andrew Boie0f9aec82012-02-14 09:32:52 -08002502 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002503 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2504 # path.
2505 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2506 if size != limit:
2507 raise ExternalError(
2508 "Mismatching image size for %s: expected %d actual %d" % (
2509 target, limit, size))
2510 else:
2511 pct = float(size) * 100.0 / limit
2512 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2513 if pct >= 99.0:
2514 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002515
2516 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002517 logger.warning("\n WARNING: %s\n", msg)
2518 else:
2519 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002520
2521
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002522def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002523 """Parses the APK certs info from a given target-files zip.
2524
2525 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2526 tuple with the following elements: (1) a dictionary that maps packages to
2527 certs (based on the "certificate" and "private_key" attributes in the file;
2528 (2) a string representing the extension of compressed APKs in the target files
2529 (e.g ".gz", ".bro").
2530
2531 Args:
2532 tf_zip: The input target_files ZipFile (already open).
2533
2534 Returns:
2535 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2536 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2537 no compressed APKs.
2538 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002539 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002540 compressed_extension = None
2541
Tao Bao0f990332017-09-08 19:02:54 -07002542 # META/apkcerts.txt contains the info for _all_ the packages known at build
2543 # time. Filter out the ones that are not installed.
2544 installed_files = set()
2545 for name in tf_zip.namelist():
2546 basename = os.path.basename(name)
2547 if basename:
2548 installed_files.add(basename)
2549
Tao Baoda30cfa2017-12-01 16:19:46 -08002550 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002551 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002552 if not line:
2553 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002554 m = re.match(
2555 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002556 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2557 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002558 line)
2559 if not m:
2560 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002561
Tao Bao818ddf52018-01-05 11:17:34 -08002562 matches = m.groupdict()
2563 cert = matches["CERT"]
2564 privkey = matches["PRIVKEY"]
2565 name = matches["NAME"]
2566 this_compressed_extension = matches["COMPRESSED"]
2567
2568 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2569 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2570 if cert in SPECIAL_CERT_STRINGS and not privkey:
2571 certmap[name] = cert
2572 elif (cert.endswith(OPTIONS.public_key_suffix) and
2573 privkey.endswith(OPTIONS.private_key_suffix) and
2574 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2575 certmap[name] = cert[:-public_key_suffix_len]
2576 else:
2577 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2578
2579 if not this_compressed_extension:
2580 continue
2581
2582 # Only count the installed files.
2583 filename = name + '.' + this_compressed_extension
2584 if filename not in installed_files:
2585 continue
2586
2587 # Make sure that all the values in the compression map have the same
2588 # extension. We don't support multiple compression methods in the same
2589 # system image.
2590 if compressed_extension:
2591 if this_compressed_extension != compressed_extension:
2592 raise ValueError(
2593 "Multiple compressed extensions: {} vs {}".format(
2594 compressed_extension, this_compressed_extension))
2595 else:
2596 compressed_extension = this_compressed_extension
2597
2598 return (certmap,
2599 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002600
2601
Doug Zongkereef39442009-04-02 12:14:19 -07002602COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002603Global options
2604
2605 -p (--path) <dir>
2606 Prepend <dir>/bin to the list of places to search for binaries run by this
2607 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002608
Doug Zongker05d3dea2009-06-22 11:32:31 -07002609 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002610 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002611
Tao Bao30df8b42018-04-23 15:32:53 -07002612 -x (--extra) <key=value>
2613 Add a key/value pair to the 'extras' dict, which device-specific extension
2614 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002615
Doug Zongkereef39442009-04-02 12:14:19 -07002616 -v (--verbose)
2617 Show command lines being executed.
2618
2619 -h (--help)
2620 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002621
2622 --logfile <file>
2623 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002624"""
2625
Kelvin Zhang0876c412020-06-23 15:06:58 -04002626
Doug Zongkereef39442009-04-02 12:14:19 -07002627def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002628 print(docstring.rstrip("\n"))
2629 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002630
2631
2632def ParseOptions(argv,
2633 docstring,
2634 extra_opts="", extra_long_opts=(),
2635 extra_option_handler=None):
2636 """Parse the options in argv and return any arguments that aren't
2637 flags. docstring is the calling module's docstring, to be displayed
2638 for errors and -h. extra_opts and extra_long_opts are for flags
2639 defined by the caller, which are processed by passing them to
2640 extra_option_handler."""
2641
2642 try:
2643 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002644 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002645 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002646 "signapk_shared_library_path=", "extra_signapk_args=",
2647 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002648 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002649 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2650 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002651 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002652 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002653 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002654 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002655 sys.exit(2)
2656
Doug Zongkereef39442009-04-02 12:14:19 -07002657 for o, a in opts:
2658 if o in ("-h", "--help"):
2659 Usage(docstring)
2660 sys.exit()
2661 elif o in ("-v", "--verbose"):
2662 OPTIONS.verbose = True
2663 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002664 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002665 elif o in ("--signapk_path",):
2666 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002667 elif o in ("--signapk_shared_library_path",):
2668 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002669 elif o in ("--extra_signapk_args",):
2670 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002671 elif o in ("--sign_sepolicy_path",):
2672 OPTIONS.sign_sepolicy_path = a
2673 elif o in ("--extra_sign_sepolicy_args",):
2674 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002675 elif o in ("--aapt2_path",):
2676 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002677 elif o in ("--java_path",):
2678 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002679 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002680 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002681 elif o in ("--android_jar_path",):
2682 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002683 elif o in ("--public_key_suffix",):
2684 OPTIONS.public_key_suffix = a
2685 elif o in ("--private_key_suffix",):
2686 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002687 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002688 raise ValueError(
2689 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002690 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002691 raise ValueError(
2692 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002693 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002694 raise ValueError(
2695 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002696 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002697 raise ValueError(
2698 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002699 elif o in ("-s", "--device_specific"):
2700 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002701 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002702 key, value = a.split("=", 1)
2703 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002704 elif o in ("--logfile",):
2705 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002706 else:
2707 if extra_option_handler is None or not extra_option_handler(o, a):
2708 assert False, "unknown option \"%s\"" % (o,)
2709
Doug Zongker85448772014-09-09 14:59:20 -07002710 if OPTIONS.search_path:
2711 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2712 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002713
2714 return args
2715
2716
Tao Bao4c851b12016-09-19 13:54:38 -07002717def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002718 """Make a temp file and add it to the list of things to be deleted
2719 when Cleanup() is called. Return the filename."""
2720 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2721 os.close(fd)
2722 OPTIONS.tempfiles.append(fn)
2723 return fn
2724
2725
Tao Bao1c830bf2017-12-25 10:43:47 -08002726def MakeTempDir(prefix='tmp', suffix=''):
2727 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2728
2729 Returns:
2730 The absolute pathname of the new directory.
2731 """
2732 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2733 OPTIONS.tempfiles.append(dir_name)
2734 return dir_name
2735
2736
Doug Zongkereef39442009-04-02 12:14:19 -07002737def Cleanup():
2738 for i in OPTIONS.tempfiles:
2739 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002740 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002741 else:
2742 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002743 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002744
2745
2746class PasswordManager(object):
2747 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002748 self.editor = os.getenv("EDITOR")
2749 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002750
2751 def GetPasswords(self, items):
2752 """Get passwords corresponding to each string in 'items',
2753 returning a dict. (The dict may have keys in addition to the
2754 values in 'items'.)
2755
2756 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2757 user edit that file to add more needed passwords. If no editor is
2758 available, or $ANDROID_PW_FILE isn't define, prompts the user
2759 interactively in the ordinary way.
2760 """
2761
2762 current = self.ReadFile()
2763
2764 first = True
2765 while True:
2766 missing = []
2767 for i in items:
2768 if i not in current or not current[i]:
2769 missing.append(i)
2770 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002771 if not missing:
2772 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002773
2774 for i in missing:
2775 current[i] = ""
2776
2777 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002778 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002779 if sys.version_info[0] >= 3:
2780 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002781 answer = raw_input("try to edit again? [y]> ").strip()
2782 if answer and answer[0] not in 'yY':
2783 raise RuntimeError("key passwords unavailable")
2784 first = False
2785
2786 current = self.UpdateAndReadFile(current)
2787
Kelvin Zhang0876c412020-06-23 15:06:58 -04002788 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002789 """Prompt the user to enter a value (password) for each key in
2790 'current' whose value is fales. Returns a new dict with all the
2791 values.
2792 """
2793 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002794 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002795 if v:
2796 result[k] = v
2797 else:
2798 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002799 result[k] = getpass.getpass(
2800 "Enter password for %s key> " % k).strip()
2801 if result[k]:
2802 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002803 return result
2804
2805 def UpdateAndReadFile(self, current):
2806 if not self.editor or not self.pwfile:
2807 return self.PromptResult(current)
2808
2809 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002810 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002811 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2812 f.write("# (Additional spaces are harmless.)\n\n")
2813
2814 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002815 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002816 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002817 f.write("[[[ %s ]]] %s\n" % (v, k))
2818 if not v and first_line is None:
2819 # position cursor on first line with no password.
2820 first_line = i + 4
2821 f.close()
2822
Tao Bao986ee862018-10-04 15:46:16 -07002823 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002824
2825 return self.ReadFile()
2826
2827 def ReadFile(self):
2828 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002829 if self.pwfile is None:
2830 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002831 try:
2832 f = open(self.pwfile, "r")
2833 for line in f:
2834 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002835 if not line or line[0] == '#':
2836 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002837 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2838 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002839 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002840 else:
2841 result[m.group(2)] = m.group(1)
2842 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002843 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002844 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002845 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002846 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002847
2848
Dan Albert8e0178d2015-01-27 15:53:15 -08002849def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2850 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002851
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002852 # http://b/18015246
2853 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2854 # for files larger than 2GiB. We can work around this by adjusting their
2855 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2856 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2857 # it isn't clear to me exactly what circumstances cause this).
2858 # `zipfile.write()` must be used directly to work around this.
2859 #
2860 # This mess can be avoided if we port to python3.
2861 saved_zip64_limit = zipfile.ZIP64_LIMIT
2862 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2863
Dan Albert8e0178d2015-01-27 15:53:15 -08002864 if compress_type is None:
2865 compress_type = zip_file.compression
2866 if arcname is None:
2867 arcname = filename
2868
2869 saved_stat = os.stat(filename)
2870
2871 try:
2872 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2873 # file to be zipped and reset it when we're done.
2874 os.chmod(filename, perms)
2875
2876 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002877 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2878 # intentional. zip stores datetimes in local time without a time zone
2879 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2880 # in the zip archive.
2881 local_epoch = datetime.datetime.fromtimestamp(0)
2882 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002883 os.utime(filename, (timestamp, timestamp))
2884
2885 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2886 finally:
2887 os.chmod(filename, saved_stat.st_mode)
2888 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002889 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08002890
2891
Tao Bao58c1b962015-05-20 09:32:18 -07002892def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002893 compress_type=None):
2894 """Wrap zipfile.writestr() function to work around the zip64 limit.
2895
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002896 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07002897 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2898 when calling crc32(bytes).
2899
2900 But it still works fine to write a shorter string into a large zip file.
2901 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2902 when we know the string won't be too long.
2903 """
2904
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002905 saved_zip64_limit = zipfile.ZIP64_LIMIT
2906 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2907
Tao Baof3282b42015-04-01 11:21:55 -07002908 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2909 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002910 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002911 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002912 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002913 else:
Tao Baof3282b42015-04-01 11:21:55 -07002914 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002915 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2916 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2917 # such a case (since
2918 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2919 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2920 # permission bits. We follow the logic in Python 3 to get consistent
2921 # behavior between using the two versions.
2922 if not zinfo.external_attr:
2923 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002924
2925 # If compress_type is given, it overrides the value in zinfo.
2926 if compress_type is not None:
2927 zinfo.compress_type = compress_type
2928
Tao Bao58c1b962015-05-20 09:32:18 -07002929 # If perms is given, it has a priority.
2930 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002931 # If perms doesn't set the file type, mark it as a regular file.
2932 if perms & 0o770000 == 0:
2933 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002934 zinfo.external_attr = perms << 16
2935
Tao Baof3282b42015-04-01 11:21:55 -07002936 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002937 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2938
Dan Albert8b72aef2015-03-23 19:13:21 -07002939 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002940 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07002941
2942
Kelvin Zhang1caead02022-09-23 10:06:03 -07002943def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08002944 """Deletes entries from a ZIP file.
2945
Tao Bao89d7ab22017-12-14 17:05:33 -08002946 Args:
2947 zip_filename: The name of the ZIP file.
2948 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08002949 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002950 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002951 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002952 # If list is empty, nothing to do
2953 if not entries:
2954 return
Wei Li8895f9e2022-10-10 17:13:17 -07002955
2956 with zipfile.ZipFile(zip_filename, 'r') as zin:
2957 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
2958 raise ExternalError(
2959 "Failed to delete zip entries, name not matched: %s" % entries)
2960
2961 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
2962 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08002963 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
2964 for entry in entries:
2965 cmd.append("-x")
2966 cmd.append(entry)
2967 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07002968
Wei Li8895f9e2022-10-10 17:13:17 -07002969
2970 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08002971
2972
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002973def ZipClose(zip_file):
2974 # http://b/18015246
2975 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2976 # central directory.
2977 saved_zip64_limit = zipfile.ZIP64_LIMIT
2978 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2979
2980 zip_file.close()
2981
2982 zipfile.ZIP64_LIMIT = saved_zip64_limit
2983
2984
Doug Zongker05d3dea2009-06-22 11:32:31 -07002985class DeviceSpecificParams(object):
2986 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002987
Doug Zongker05d3dea2009-06-22 11:32:31 -07002988 def __init__(self, **kwargs):
2989 """Keyword arguments to the constructor become attributes of this
2990 object, which is passed to all functions in the device-specific
2991 module."""
Tao Bao38884282019-07-10 22:20:56 -07002992 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002993 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002994 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002995
2996 if self.module is None:
2997 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002998 if not path:
2999 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003000 try:
3001 if os.path.isdir(path):
3002 info = imp.find_module("releasetools", [path])
3003 else:
3004 d, f = os.path.split(path)
3005 b, x = os.path.splitext(f)
3006 if x == ".py":
3007 f = b
3008 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07003009 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07003010 self.module = imp.load_module("device_specific", *info)
3011 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07003012 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003013
3014 def _DoCall(self, function_name, *args, **kwargs):
3015 """Call the named function in the device-specific module, passing
3016 the given args and kwargs. The first argument to the call will be
3017 the DeviceSpecific object itself. If there is no module, or the
3018 module does not define the function, return the value of the
3019 'default' kwarg (which itself defaults to None)."""
3020 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08003021 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07003022 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
3023
3024 def FullOTA_Assertions(self):
3025 """Called after emitting the block of assertions at the top of a
3026 full OTA package. Implementations can add whatever additional
3027 assertions they like."""
3028 return self._DoCall("FullOTA_Assertions")
3029
Doug Zongkere5ff5902012-01-17 10:55:37 -08003030 def FullOTA_InstallBegin(self):
3031 """Called at the start of full OTA installation."""
3032 return self._DoCall("FullOTA_InstallBegin")
3033
Yifan Hong10c530d2018-12-27 17:34:18 -08003034 def FullOTA_GetBlockDifferences(self):
3035 """Called during full OTA installation and verification.
3036 Implementation should return a list of BlockDifference objects describing
3037 the update on each additional partitions.
3038 """
3039 return self._DoCall("FullOTA_GetBlockDifferences")
3040
Doug Zongker05d3dea2009-06-22 11:32:31 -07003041 def FullOTA_InstallEnd(self):
3042 """Called at the end of full OTA installation; typically this is
3043 used to install the image for the device's baseband processor."""
3044 return self._DoCall("FullOTA_InstallEnd")
3045
3046 def IncrementalOTA_Assertions(self):
3047 """Called after emitting the block of assertions at the top of an
3048 incremental OTA package. Implementations can add whatever
3049 additional assertions they like."""
3050 return self._DoCall("IncrementalOTA_Assertions")
3051
Doug Zongkere5ff5902012-01-17 10:55:37 -08003052 def IncrementalOTA_VerifyBegin(self):
3053 """Called at the start of the verification phase of incremental
3054 OTA installation; additional checks can be placed here to abort
3055 the script before any changes are made."""
3056 return self._DoCall("IncrementalOTA_VerifyBegin")
3057
Doug Zongker05d3dea2009-06-22 11:32:31 -07003058 def IncrementalOTA_VerifyEnd(self):
3059 """Called at the end of the verification phase of incremental OTA
3060 installation; additional checks can be placed here to abort the
3061 script before any changes are made."""
3062 return self._DoCall("IncrementalOTA_VerifyEnd")
3063
Doug Zongkere5ff5902012-01-17 10:55:37 -08003064 def IncrementalOTA_InstallBegin(self):
3065 """Called at the start of incremental OTA installation (after
3066 verification is complete)."""
3067 return self._DoCall("IncrementalOTA_InstallBegin")
3068
Yifan Hong10c530d2018-12-27 17:34:18 -08003069 def IncrementalOTA_GetBlockDifferences(self):
3070 """Called during incremental OTA installation and verification.
3071 Implementation should return a list of BlockDifference objects describing
3072 the update on each additional partitions.
3073 """
3074 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3075
Doug Zongker05d3dea2009-06-22 11:32:31 -07003076 def IncrementalOTA_InstallEnd(self):
3077 """Called at the end of incremental OTA installation; typically
3078 this is used to install the image for the device's baseband
3079 processor."""
3080 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003081
Tao Bao9bc6bb22015-11-09 16:58:28 -08003082 def VerifyOTA_Assertions(self):
3083 return self._DoCall("VerifyOTA_Assertions")
3084
Tao Bao76def242017-11-21 09:25:31 -08003085
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003086class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003087 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003088 self.name = name
3089 self.data = data
3090 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003091 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003092 self.sha1 = sha1(data).hexdigest()
3093
3094 @classmethod
3095 def FromLocalFile(cls, name, diskname):
3096 f = open(diskname, "rb")
3097 data = f.read()
3098 f.close()
3099 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003100
3101 def WriteToTemp(self):
3102 t = tempfile.NamedTemporaryFile()
3103 t.write(self.data)
3104 t.flush()
3105 return t
3106
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003107 def WriteToDir(self, d):
3108 with open(os.path.join(d, self.name), "wb") as fp:
3109 fp.write(self.data)
3110
Geremy Condra36bd3652014-02-06 19:45:10 -08003111 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003112 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003113
Tao Bao76def242017-11-21 09:25:31 -08003114
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003115DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003116 ".gz": "imgdiff",
3117 ".zip": ["imgdiff", "-z"],
3118 ".jar": ["imgdiff", "-z"],
3119 ".apk": ["imgdiff", "-z"],
3120 ".img": "imgdiff",
3121}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003122
Tao Bao76def242017-11-21 09:25:31 -08003123
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003124class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003125 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003126 self.tf = tf
3127 self.sf = sf
3128 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003129 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003130
3131 def ComputePatch(self):
3132 """Compute the patch (as a string of data) needed to turn sf into
3133 tf. Returns the same tuple as GetPatch()."""
3134
3135 tf = self.tf
3136 sf = self.sf
3137
Doug Zongker24cd2802012-08-14 16:36:15 -07003138 if self.diff_program:
3139 diff_program = self.diff_program
3140 else:
3141 ext = os.path.splitext(tf.name)[1]
3142 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003143
3144 ttemp = tf.WriteToTemp()
3145 stemp = sf.WriteToTemp()
3146
3147 ext = os.path.splitext(tf.name)[1]
3148
3149 try:
3150 ptemp = tempfile.NamedTemporaryFile()
3151 if isinstance(diff_program, list):
3152 cmd = copy.copy(diff_program)
3153 else:
3154 cmd = [diff_program]
3155 cmd.append(stemp.name)
3156 cmd.append(ttemp.name)
3157 cmd.append(ptemp.name)
3158 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003159 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003160
Doug Zongkerf8340082014-08-05 10:39:37 -07003161 def run():
3162 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003163 if e:
3164 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003165 th = threading.Thread(target=run)
3166 th.start()
3167 th.join(timeout=300) # 5 mins
3168 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003169 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003170 p.terminate()
3171 th.join(5)
3172 if th.is_alive():
3173 p.kill()
3174 th.join()
3175
Tianjie Xua2a9f992018-01-05 15:15:54 -08003176 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003177 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003178 self.patch = None
3179 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003180 diff = ptemp.read()
3181 finally:
3182 ptemp.close()
3183 stemp.close()
3184 ttemp.close()
3185
3186 self.patch = diff
3187 return self.tf, self.sf, self.patch
3188
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003189 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003190 """Returns a tuple of (target_file, source_file, patch_data).
3191
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003192 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003193 computing the patch failed.
3194 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003195 return self.tf, self.sf, self.patch
3196
3197
3198def ComputeDifferences(diffs):
3199 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003200 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003201
3202 # Do the largest files first, to try and reduce the long-pole effect.
3203 by_size = [(i.tf.size, i) for i in diffs]
3204 by_size.sort(reverse=True)
3205 by_size = [i[1] for i in by_size]
3206
3207 lock = threading.Lock()
3208 diff_iter = iter(by_size) # accessed under lock
3209
3210 def worker():
3211 try:
3212 lock.acquire()
3213 for d in diff_iter:
3214 lock.release()
3215 start = time.time()
3216 d.ComputePatch()
3217 dur = time.time() - start
3218 lock.acquire()
3219
3220 tf, sf, patch = d.GetPatch()
3221 if sf.name == tf.name:
3222 name = tf.name
3223 else:
3224 name = "%s (%s)" % (tf.name, sf.name)
3225 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003226 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003227 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003228 logger.info(
3229 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3230 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003231 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003232 except Exception:
3233 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003234 raise
3235
3236 # start worker threads; wait for them all to finish.
3237 threads = [threading.Thread(target=worker)
3238 for i in range(OPTIONS.worker_threads)]
3239 for th in threads:
3240 th.start()
3241 while threads:
3242 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003243
3244
Dan Albert8b72aef2015-03-23 19:13:21 -07003245class BlockDifference(object):
3246 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003247 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003248 self.tgt = tgt
3249 self.src = src
3250 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003251 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003252 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003253
Tao Baodd2a5892015-03-12 12:32:37 -07003254 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003255 version = max(
3256 int(i) for i in
3257 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003258 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003259 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003260
Tianjie Xu41976c72019-07-03 13:57:01 -07003261 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3262 version=self.version,
3263 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003264 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003265 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003266 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003267 self.touched_src_ranges = b.touched_src_ranges
3268 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003269
Yifan Hong10c530d2018-12-27 17:34:18 -08003270 # On devices with dynamic partitions, for new partitions,
3271 # src is None but OPTIONS.source_info_dict is not.
3272 if OPTIONS.source_info_dict is None:
3273 is_dynamic_build = OPTIONS.info_dict.get(
3274 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003275 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003276 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003277 is_dynamic_build = OPTIONS.source_info_dict.get(
3278 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003279 is_dynamic_source = partition in shlex.split(
3280 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003281
Yifan Hongbb2658d2019-01-25 12:30:58 -08003282 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003283 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3284
Yifan Hongbb2658d2019-01-25 12:30:58 -08003285 # For dynamic partitions builds, check partition list in both source
3286 # and target build because new partitions may be added, and existing
3287 # partitions may be removed.
3288 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3289
Yifan Hong10c530d2018-12-27 17:34:18 -08003290 if is_dynamic:
3291 self.device = 'map_partition("%s")' % partition
3292 else:
3293 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003294 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3295 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003296 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003297 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3298 OPTIONS.source_info_dict)
3299 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003300
Tao Baod8d14be2016-02-04 14:26:02 -08003301 @property
3302 def required_cache(self):
3303 return self._required_cache
3304
Tao Bao76def242017-11-21 09:25:31 -08003305 def WriteScript(self, script, output_zip, progress=None,
3306 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003307 if not self.src:
3308 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003309 script.Print("Patching %s image unconditionally..." % (self.partition,))
3310 else:
3311 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003312
Dan Albert8b72aef2015-03-23 19:13:21 -07003313 if progress:
3314 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003315 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003316
3317 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003318 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003319
Tao Bao9bc6bb22015-11-09 16:58:28 -08003320 def WriteStrictVerifyScript(self, script):
3321 """Verify all the blocks in the care_map, including clobbered blocks.
3322
3323 This differs from the WriteVerifyScript() function: a) it prints different
3324 error messages; b) it doesn't allow half-way updated images to pass the
3325 verification."""
3326
3327 partition = self.partition
3328 script.Print("Verifying %s..." % (partition,))
3329 ranges = self.tgt.care_map
3330 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003331 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003332 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3333 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003334 self.device, ranges_str,
3335 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003336 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003337 script.AppendExtra("")
3338
Tao Baod522bdc2016-04-12 15:53:16 -07003339 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003340 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003341
3342 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003343 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003344 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003345
3346 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003347 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003348 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003349 ranges = self.touched_src_ranges
3350 expected_sha1 = self.touched_src_sha1
3351 else:
3352 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3353 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003354
3355 # No blocks to be checked, skipping.
3356 if not ranges:
3357 return
3358
Tao Bao5ece99d2015-05-12 11:42:31 -07003359 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003360 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003361 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003362 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3363 '"%s.patch.dat")) then' % (
3364 self.device, ranges_str, expected_sha1,
3365 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003366 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003367 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003368
Tianjie Xufc3422a2015-12-15 11:53:59 -08003369 if self.version >= 4:
3370
3371 # Bug: 21124327
3372 # When generating incrementals for the system and vendor partitions in
3373 # version 4 or newer, explicitly check the first block (which contains
3374 # the superblock) of the partition to see if it's what we expect. If
3375 # this check fails, give an explicit log message about the partition
3376 # having been remounted R/W (the most likely explanation).
3377 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003378 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003379
3380 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003381 if partition == "system":
3382 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3383 else:
3384 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003385 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003386 'ifelse (block_image_recover({device}, "{ranges}") && '
3387 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003388 'package_extract_file("{partition}.transfer.list"), '
3389 '"{partition}.new.dat", "{partition}.patch.dat"), '
3390 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003391 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003392 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003393 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003394
Tao Baodd2a5892015-03-12 12:32:37 -07003395 # Abort the OTA update. Note that the incremental OTA cannot be applied
3396 # even if it may match the checksum of the target partition.
3397 # a) If version < 3, operations like move and erase will make changes
3398 # unconditionally and damage the partition.
3399 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003400 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003401 if partition == "system":
3402 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3403 else:
3404 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3405 script.AppendExtra((
3406 'abort("E%d: %s partition has unexpected contents");\n'
3407 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003408
Yifan Hong10c530d2018-12-27 17:34:18 -08003409 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003410 partition = self.partition
3411 script.Print('Verifying the updated %s image...' % (partition,))
3412 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3413 ranges = self.tgt.care_map
3414 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003415 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003416 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003417 self.device, ranges_str,
3418 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003419
3420 # Bug: 20881595
3421 # Verify that extended blocks are really zeroed out.
3422 if self.tgt.extended:
3423 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003424 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003425 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003426 self.device, ranges_str,
3427 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003428 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003429 if partition == "system":
3430 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3431 else:
3432 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003433 script.AppendExtra(
3434 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003435 ' abort("E%d: %s partition has unexpected non-zero contents after '
3436 'OTA update");\n'
3437 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003438 else:
3439 script.Print('Verified the updated %s image.' % (partition,))
3440
Tianjie Xu209db462016-05-24 17:34:52 -07003441 if partition == "system":
3442 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3443 else:
3444 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3445
Tao Bao5fcaaef2015-06-01 13:40:49 -07003446 script.AppendExtra(
3447 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003448 ' abort("E%d: %s partition has unexpected contents after OTA '
3449 'update");\n'
3450 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003451
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003452 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003453 ZipWrite(output_zip,
3454 '{}.transfer.list'.format(self.path),
3455 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003456
Tao Bao76def242017-11-21 09:25:31 -08003457 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3458 # its size. Quailty 9 almost triples the compression time but doesn't
3459 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003460 # zip | brotli(quality 6) | brotli(quality 9)
3461 # compressed_size: 942M | 869M (~8% reduced) | 854M
3462 # compression_time: 75s | 265s | 719s
3463 # decompression_time: 15s | 25s | 25s
3464
3465 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003466 brotli_cmd = ['brotli', '--quality=6',
3467 '--output={}.new.dat.br'.format(self.path),
3468 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003469 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003470 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003471
3472 new_data_name = '{}.new.dat.br'.format(self.partition)
3473 ZipWrite(output_zip,
3474 '{}.new.dat.br'.format(self.path),
3475 new_data_name,
3476 compress_type=zipfile.ZIP_STORED)
3477 else:
3478 new_data_name = '{}.new.dat'.format(self.partition)
3479 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3480
Dan Albert8e0178d2015-01-27 15:53:15 -08003481 ZipWrite(output_zip,
3482 '{}.patch.dat'.format(self.path),
3483 '{}.patch.dat'.format(self.partition),
3484 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003485
Tianjie Xu209db462016-05-24 17:34:52 -07003486 if self.partition == "system":
3487 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3488 else:
3489 code = ErrorCode.VENDOR_UPDATE_FAILURE
3490
Yifan Hong10c530d2018-12-27 17:34:18 -08003491 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003492 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003493 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003494 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003495 device=self.device, partition=self.partition,
3496 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003497 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003498
Kelvin Zhang0876c412020-06-23 15:06:58 -04003499 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003500 data = source.ReadRangeSet(ranges)
3501 ctx = sha1()
3502
3503 for p in data:
3504 ctx.update(p)
3505
3506 return ctx.hexdigest()
3507
Kelvin Zhang0876c412020-06-23 15:06:58 -04003508 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003509 """Return the hash value for all zero blocks."""
3510 zero_block = '\x00' * 4096
3511 ctx = sha1()
3512 for _ in range(num_blocks):
3513 ctx.update(zero_block)
3514
3515 return ctx.hexdigest()
3516
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003517
Tianjie Xu41976c72019-07-03 13:57:01 -07003518# Expose these two classes to support vendor-specific scripts
3519DataImage = images.DataImage
3520EmptyImage = images.EmptyImage
3521
Tao Bao76def242017-11-21 09:25:31 -08003522
Doug Zongker96a57e72010-09-26 14:57:41 -07003523# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003524PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003525 "ext4": "EMMC",
3526 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003527 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003528 "squashfs": "EMMC",
3529 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003530}
Doug Zongker96a57e72010-09-26 14:57:41 -07003531
Kelvin Zhang0876c412020-06-23 15:06:58 -04003532
Yifan Hongbdb32012020-05-07 12:38:53 -07003533def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3534 """
3535 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3536 backwards compatibility. It aborts if the fstab entry has slotselect option
3537 (unless check_no_slot is explicitly set to False).
3538 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003539 fstab = info["fstab"]
3540 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003541 if check_no_slot:
3542 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003543 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003544 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3545 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003546 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003547
3548
Yifan Hongbdb32012020-05-07 12:38:53 -07003549def GetTypeAndDeviceExpr(mount_point, info):
3550 """
3551 Return the filesystem of the partition, and an edify expression that evaluates
3552 to the device at runtime.
3553 """
3554 fstab = info["fstab"]
3555 if fstab:
3556 p = fstab[mount_point]
3557 device_expr = '"%s"' % fstab[mount_point].device
3558 if p.slotselect:
3559 device_expr = 'add_slot_suffix(%s)' % device_expr
3560 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003561 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003562
3563
3564def GetEntryForDevice(fstab, device):
3565 """
3566 Returns:
3567 The first entry in fstab whose device is the given value.
3568 """
3569 if not fstab:
3570 return None
3571 for mount_point in fstab:
3572 if fstab[mount_point].device == device:
3573 return fstab[mount_point]
3574 return None
3575
Kelvin Zhang0876c412020-06-23 15:06:58 -04003576
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003577def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003578 """Parses and converts a PEM-encoded certificate into DER-encoded.
3579
3580 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3581
3582 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003583 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003584 """
3585 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003586 save = False
3587 for line in data.split("\n"):
3588 if "--END CERTIFICATE--" in line:
3589 break
3590 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003591 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003592 if "--BEGIN CERTIFICATE--" in line:
3593 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003594 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003595 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003596
Tao Bao04e1f012018-02-04 12:13:35 -08003597
3598def ExtractPublicKey(cert):
3599 """Extracts the public key (PEM-encoded) from the given certificate file.
3600
3601 Args:
3602 cert: The certificate filename.
3603
3604 Returns:
3605 The public key string.
3606
3607 Raises:
3608 AssertionError: On non-zero return from 'openssl'.
3609 """
3610 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3611 # While openssl 1.1 writes the key into the given filename followed by '-out',
3612 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3613 # stdout instead.
3614 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3615 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3616 pubkey, stderrdata = proc.communicate()
3617 assert proc.returncode == 0, \
3618 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3619 return pubkey
3620
3621
Tao Bao1ac886e2019-06-26 11:58:22 -07003622def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003623 """Extracts the AVB public key from the given public or private key.
3624
3625 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003626 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003627 key: The input key file, which should be PEM-encoded public or private key.
3628
3629 Returns:
3630 The path to the extracted AVB public key file.
3631 """
3632 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3633 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003634 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003635 return output
3636
3637
Doug Zongker412c02f2014-02-13 10:58:24 -08003638def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3639 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003640 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003641
Tao Bao6d5d6232018-03-09 17:04:42 -08003642 Most of the space in the boot and recovery images is just the kernel, which is
3643 identical for the two, so the resulting patch should be efficient. Add it to
3644 the output zip, along with a shell script that is run from init.rc on first
3645 boot to actually do the patching and install the new recovery image.
3646
3647 Args:
3648 input_dir: The top-level input directory of the target-files.zip.
3649 output_sink: The callback function that writes the result.
3650 recovery_img: File object for the recovery image.
3651 boot_img: File objects for the boot image.
3652 info_dict: A dict returned by common.LoadInfoDict() on the input
3653 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003654 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003655 if info_dict is None:
3656 info_dict = OPTIONS.info_dict
3657
Tao Bao6d5d6232018-03-09 17:04:42 -08003658 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003659 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3660
3661 if board_uses_vendorimage:
3662 # In this case, the output sink is rooted at VENDOR
3663 recovery_img_path = "etc/recovery.img"
3664 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3665 sh_dir = "bin"
3666 else:
3667 # In this case the output sink is rooted at SYSTEM
3668 recovery_img_path = "vendor/etc/recovery.img"
3669 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3670 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003671
Tao Baof2cffbd2015-07-22 12:33:18 -07003672 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003673 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003674
3675 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003676 system_root_image = info_dict.get("system_root_image") == "true"
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003677 include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
3678 include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003679 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003680 # With system-root-image, boot and recovery images will have mismatching
3681 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3682 # to handle such a case.
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003683 if system_root_image or include_recovery_dtbo or include_recovery_acpio:
Tao Bao6d5d6232018-03-09 17:04:42 -08003684 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003685 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003686 assert not os.path.exists(path)
3687 else:
3688 diff_program = ["imgdiff"]
3689 if os.path.exists(path):
3690 diff_program.append("-b")
3691 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003692 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003693 else:
3694 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003695
3696 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3697 _, _, patch = d.ComputePatch()
3698 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003699
Dan Albertebb19aa2015-03-27 19:11:53 -07003700 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003701 # The following GetTypeAndDevice()s need to use the path in the target
3702 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003703 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3704 check_no_slot=False)
3705 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3706 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003707 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003708 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003709
Tao Baof2cffbd2015-07-22 12:33:18 -07003710 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003711
3712 # Note that we use /vendor to refer to the recovery resources. This will
3713 # work for a separate vendor partition mounted at /vendor or a
3714 # /system/vendor subdirectory on the system partition, for which init will
3715 # create a symlink from /vendor to /system/vendor.
3716
3717 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003718if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3719 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003720 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003721 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3722 log -t recovery "Installing new recovery image: succeeded" || \\
3723 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003724else
3725 log -t recovery "Recovery image already installed"
3726fi
3727""" % {'type': recovery_type,
3728 'device': recovery_device,
3729 'sha1': recovery_img.sha1,
3730 'size': recovery_img.size}
3731 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003732 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003733if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3734 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003735 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003736 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3737 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3738 log -t recovery "Installing new recovery image: succeeded" || \\
3739 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003740else
3741 log -t recovery "Recovery image already installed"
3742fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003743""" % {'boot_size': boot_img.size,
3744 'boot_sha1': boot_img.sha1,
3745 'recovery_size': recovery_img.size,
3746 'recovery_sha1': recovery_img.sha1,
3747 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003748 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003749 'recovery_type': recovery_type,
3750 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003751 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003752
Bill Peckhame868aec2019-09-17 17:06:47 -07003753 # The install script location moved from /system/etc to /system/bin in the L
3754 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3755 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003756
Tao Bao32fcdab2018-10-12 10:30:39 -07003757 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003758
Tao Baoda30cfa2017-12-01 16:19:46 -08003759 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003760
3761
3762class DynamicPartitionUpdate(object):
3763 def __init__(self, src_group=None, tgt_group=None, progress=None,
3764 block_difference=None):
3765 self.src_group = src_group
3766 self.tgt_group = tgt_group
3767 self.progress = progress
3768 self.block_difference = block_difference
3769
3770 @property
3771 def src_size(self):
3772 if not self.block_difference:
3773 return 0
3774 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3775
3776 @property
3777 def tgt_size(self):
3778 if not self.block_difference:
3779 return 0
3780 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3781
3782 @staticmethod
3783 def _GetSparseImageSize(img):
3784 if not img:
3785 return 0
3786 return img.blocksize * img.total_blocks
3787
3788
3789class DynamicGroupUpdate(object):
3790 def __init__(self, src_size=None, tgt_size=None):
3791 # None: group does not exist. 0: no size limits.
3792 self.src_size = src_size
3793 self.tgt_size = tgt_size
3794
3795
3796class DynamicPartitionsDifference(object):
3797 def __init__(self, info_dict, block_diffs, progress_dict=None,
3798 source_info_dict=None):
3799 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003800 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003801
3802 self._remove_all_before_apply = False
3803 if source_info_dict is None:
3804 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003805 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003806
Tao Baof1113e92019-06-18 12:10:14 -07003807 block_diff_dict = collections.OrderedDict(
3808 [(e.partition, e) for e in block_diffs])
3809
Yifan Hong10c530d2018-12-27 17:34:18 -08003810 assert len(block_diff_dict) == len(block_diffs), \
3811 "Duplicated BlockDifference object for {}".format(
3812 [partition for partition, count in
3813 collections.Counter(e.partition for e in block_diffs).items()
3814 if count > 1])
3815
Yifan Hong79997e52019-01-23 16:56:19 -08003816 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003817
3818 for p, block_diff in block_diff_dict.items():
3819 self._partition_updates[p] = DynamicPartitionUpdate()
3820 self._partition_updates[p].block_difference = block_diff
3821
3822 for p, progress in progress_dict.items():
3823 if p in self._partition_updates:
3824 self._partition_updates[p].progress = progress
3825
3826 tgt_groups = shlex.split(info_dict.get(
3827 "super_partition_groups", "").strip())
3828 src_groups = shlex.split(source_info_dict.get(
3829 "super_partition_groups", "").strip())
3830
3831 for g in tgt_groups:
3832 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003833 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003834 assert p in self._partition_updates, \
3835 "{} is in target super_{}_partition_list but no BlockDifference " \
3836 "object is provided.".format(p, g)
3837 self._partition_updates[p].tgt_group = g
3838
3839 for g in src_groups:
3840 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003841 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003842 assert p in self._partition_updates, \
3843 "{} is in source super_{}_partition_list but no BlockDifference " \
3844 "object is provided.".format(p, g)
3845 self._partition_updates[p].src_group = g
3846
Yifan Hong45433e42019-01-18 13:55:25 -08003847 target_dynamic_partitions = set(shlex.split(info_dict.get(
3848 "dynamic_partition_list", "").strip()))
3849 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3850 if u.tgt_size)
3851 assert block_diffs_with_target == target_dynamic_partitions, \
3852 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3853 list(target_dynamic_partitions), list(block_diffs_with_target))
3854
3855 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3856 "dynamic_partition_list", "").strip()))
3857 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3858 if u.src_size)
3859 assert block_diffs_with_source == source_dynamic_partitions, \
3860 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3861 list(source_dynamic_partitions), list(block_diffs_with_source))
3862
Yifan Hong10c530d2018-12-27 17:34:18 -08003863 if self._partition_updates:
3864 logger.info("Updating dynamic partitions %s",
3865 self._partition_updates.keys())
3866
Yifan Hong79997e52019-01-23 16:56:19 -08003867 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003868
3869 for g in tgt_groups:
3870 self._group_updates[g] = DynamicGroupUpdate()
3871 self._group_updates[g].tgt_size = int(info_dict.get(
3872 "super_%s_group_size" % g, "0").strip())
3873
3874 for g in src_groups:
3875 if g not in self._group_updates:
3876 self._group_updates[g] = DynamicGroupUpdate()
3877 self._group_updates[g].src_size = int(source_info_dict.get(
3878 "super_%s_group_size" % g, "0").strip())
3879
3880 self._Compute()
3881
3882 def WriteScript(self, script, output_zip, write_verify_script=False):
3883 script.Comment('--- Start patching dynamic partitions ---')
3884 for p, u in self._partition_updates.items():
3885 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3886 script.Comment('Patch partition %s' % p)
3887 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3888 write_verify_script=False)
3889
3890 op_list_path = MakeTempFile()
3891 with open(op_list_path, 'w') as f:
3892 for line in self._op_list:
3893 f.write('{}\n'.format(line))
3894
3895 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3896
3897 script.Comment('Update dynamic partition metadata')
3898 script.AppendExtra('assert(update_dynamic_partitions('
3899 'package_extract_file("dynamic_partitions_op_list")));')
3900
3901 if write_verify_script:
3902 for p, u in self._partition_updates.items():
3903 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3904 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003905 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003906
3907 for p, u in self._partition_updates.items():
3908 if u.tgt_size and u.src_size <= u.tgt_size:
3909 script.Comment('Patch partition %s' % p)
3910 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3911 write_verify_script=write_verify_script)
3912 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003913 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003914
3915 script.Comment('--- End patching dynamic partitions ---')
3916
3917 def _Compute(self):
3918 self._op_list = list()
3919
3920 def append(line):
3921 self._op_list.append(line)
3922
3923 def comment(line):
3924 self._op_list.append("# %s" % line)
3925
3926 if self._remove_all_before_apply:
3927 comment('Remove all existing dynamic partitions and groups before '
3928 'applying full OTA')
3929 append('remove_all_groups')
3930
3931 for p, u in self._partition_updates.items():
3932 if u.src_group and not u.tgt_group:
3933 append('remove %s' % p)
3934
3935 for p, u in self._partition_updates.items():
3936 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3937 comment('Move partition %s from %s to default' % (p, u.src_group))
3938 append('move %s default' % p)
3939
3940 for p, u in self._partition_updates.items():
3941 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3942 comment('Shrink partition %s from %d to %d' %
3943 (p, u.src_size, u.tgt_size))
3944 append('resize %s %s' % (p, u.tgt_size))
3945
3946 for g, u in self._group_updates.items():
3947 if u.src_size is not None and u.tgt_size is None:
3948 append('remove_group %s' % g)
3949 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003950 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003951 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3952 append('resize_group %s %d' % (g, u.tgt_size))
3953
3954 for g, u in self._group_updates.items():
3955 if u.src_size is None and u.tgt_size is not None:
3956 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3957 append('add_group %s %d' % (g, u.tgt_size))
3958 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003959 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003960 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3961 append('resize_group %s %d' % (g, u.tgt_size))
3962
3963 for p, u in self._partition_updates.items():
3964 if u.tgt_group and not u.src_group:
3965 comment('Add partition %s to group %s' % (p, u.tgt_group))
3966 append('add %s %s' % (p, u.tgt_group))
3967
3968 for p, u in self._partition_updates.items():
3969 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003970 comment('Grow partition %s from %d to %d' %
3971 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003972 append('resize %s %d' % (p, u.tgt_size))
3973
3974 for p, u in self._partition_updates.items():
3975 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3976 comment('Move partition %s from default to %s' %
3977 (p, u.tgt_group))
3978 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003979
3980
jiajia tangf3f842b2021-03-17 21:49:44 +08003981def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003982 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003983 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003984
3985 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003986 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003987
3988 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003989 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003990 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003991 tmp_dir = MakeTempDir('boot_', suffix='.img')
3992 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003993 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3994 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003995 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3996 if not os.path.isfile(ramdisk):
3997 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3998 return None
3999 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08004000 if ramdisk_format == RamdiskFormat.LZ4:
4001 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
4002 elif ramdisk_format == RamdiskFormat.GZ:
4003 with open(ramdisk, 'rb') as input_stream:
4004 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04004005 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
4006 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08004007 p2.wait()
4008 else:
4009 logger.error('Only support lz4 or minigzip ramdisk format.')
4010 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004011
4012 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
4013 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
4014 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
4015 # the host environment.
4016 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04004017 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08004018
Yifan Hongc65a0542021-01-07 14:21:01 -08004019 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
4020 prop_file = os.path.join(extracted_ramdisk, search_path)
4021 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08004022 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04004023 logger.warning(
4024 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08004025
Yifan Hong7dc51172021-01-12 11:27:39 -08004026 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004027
Yifan Hong85ac5012021-01-07 14:43:46 -08004028 except ExternalError as e:
4029 logger.warning('Unable to get boot image build props: %s', e)
4030 return None
4031
4032
4033def GetBootImageTimestamp(boot_img):
4034 """
4035 Get timestamp from ramdisk within the boot image
4036
4037 Args:
4038 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
4039
4040 Return:
4041 An integer that corresponds to the timestamp of the boot image, or None
4042 if file has unknown format. Raise exception if an unexpected error has
4043 occurred.
4044 """
4045 prop_file = GetBootImageBuildProp(boot_img)
4046 if not prop_file:
4047 return None
4048
4049 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4050 if props is None:
4051 return None
4052
4053 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004054 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4055 if timestamp:
4056 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004057 logger.warning(
4058 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004059 return None
4060
4061 except ExternalError as e:
4062 logger.warning('Unable to get boot image timestamp: %s', e)
4063 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004064
4065
Kelvin Zhang26390482021-11-02 14:31:10 -07004066def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004067 if not os.path.exists(filepath):
4068 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004069 with open(filepath, 'rb') as fp:
4070 # Magic for android sparse image format
4071 # https://source.android.com/devices/bootloader/images
4072 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004073
4074def ParseUpdateEngineConfig(path: str):
4075 """Parse the update_engine config stored in file `path`
4076 Args
4077 path: Path to update_engine_config.txt file in target_files
4078
4079 Returns
4080 A tuple of (major, minor) version number . E.g. (2, 8)
4081 """
4082 with open(path, "r") as fp:
4083 # update_engine_config.txt is only supposed to contain two lines,
4084 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
4085 # sufficient. If the length is more than that, something is wrong.
4086 data = fp.read(1024)
4087 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
4088 if not major:
4089 raise ValueError(
4090 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
4091 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
4092 if not minor:
4093 raise ValueError(
4094 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
4095 return (int(major.group(1)), int(minor.group(1)))