blob: 6681919b8d4aaba1ef1761da88050ef150ce6162 [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"
1379 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001380 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1381 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1382 if os.path.exists(new_key_path):
1383 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001384 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1385 if key_path and algorithm:
1386 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001387 avb_salt = OPTIONS.info_dict.get("avb_salt")
1388 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001389 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001390 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001391
1392
Tao Bao765668f2019-10-04 22:03:00 -07001393def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001394 """Returns the VBMeta arguments for partition.
1395
1396 It sets up the VBMeta argument by including the partition descriptor from the
1397 given 'image', or by configuring the partition as a chained partition.
1398
1399 Args:
1400 partition: The name of the partition (e.g. "system").
1401 image: The path to the partition image.
1402 info_dict: A dict returned by common.LoadInfoDict(). Will use
1403 OPTIONS.info_dict if None has been given.
1404
1405 Returns:
1406 A list of VBMeta arguments.
1407 """
1408 if info_dict is None:
1409 info_dict = OPTIONS.info_dict
1410
1411 # Check if chain partition is used.
1412 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001413 if not key_path:
1414 return ["--include_descriptors_from_image", image]
1415
1416 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1417 # into vbmeta.img. The recovery image will be configured on an independent
1418 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1419 # See details at
1420 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001421 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001422 return []
1423
1424 # Otherwise chain the partition into vbmeta.
1425 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1426 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001427
1428
Tao Bao02a08592018-07-22 12:40:45 -07001429def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1430 """Constructs and returns the arg to build or verify a chained partition.
1431
1432 Args:
1433 partition: The partition name.
1434 info_dict: The info dict to look up the key info and rollback index
1435 location.
1436 key: The key to be used for building or verifying the partition. Defaults to
1437 the key listed in info_dict.
1438
1439 Returns:
1440 A string of form "partition:rollback_index_location:key" that can be used to
1441 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001442 """
1443 if key is None:
1444 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001445 if key and not os.path.exists(key) and OPTIONS.search_path:
1446 new_key_path = os.path.join(OPTIONS.search_path, key)
1447 if os.path.exists(new_key_path):
1448 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001449 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001450 rollback_index_location = info_dict[
1451 "avb_" + partition + "_rollback_index_location"]
1452 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1453
1454
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001455def _HasGkiCertificationArgs():
1456 return ("gki_signing_key_path" in OPTIONS.info_dict and
1457 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001458
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001459
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001460def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001461 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001462 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001463
1464 if not os.path.exists(key_path) and OPTIONS.search_path:
1465 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1466 if os.path.exists(new_key_path):
1467 key_path = new_key_path
1468
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001469 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001470 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001471 raise ExternalError(
1472 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001473
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001474 output_certificate = tempfile.NamedTemporaryFile()
1475 cmd = [
1476 "generate_gki_certificate",
1477 "--name", image_name,
1478 "--algorithm", algorithm,
1479 "--key", key_path,
1480 "--output", output_certificate.name,
1481 image,
1482 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001483
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001484 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1485 signature_args = signature_args.strip()
1486 if signature_args:
1487 cmd.extend(["--additional_avb_args", signature_args])
1488
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001489 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001490 args = args.strip()
1491 if args:
1492 cmd.extend(["--additional_avb_args", args])
1493
1494 RunAndCheckOutput(cmd)
1495
1496 output_certificate.seek(os.SEEK_SET, 0)
1497 data = output_certificate.read()
1498 output_certificate.close()
1499 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001500
1501
Daniel Norman276f0622019-07-26 14:13:51 -07001502def BuildVBMeta(image_path, partitions, name, needed_partitions):
1503 """Creates a VBMeta image.
1504
1505 It generates the requested VBMeta image. The requested image could be for
1506 top-level or chained VBMeta image, which is determined based on the name.
1507
1508 Args:
1509 image_path: The output path for the new VBMeta image.
1510 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001511 values. Only valid partition names are accepted, as partitions listed
1512 in common.AVB_PARTITIONS and custom partitions listed in
1513 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001514 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1515 needed_partitions: Partitions whose descriptors should be included into the
1516 generated VBMeta image.
1517
1518 Raises:
1519 AssertionError: On invalid input args.
1520 """
1521 avbtool = OPTIONS.info_dict["avb_avbtool"]
1522 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1523 AppendAVBSigningArgs(cmd, name)
1524
Hongguang Chenf23364d2020-04-27 18:36:36 -07001525 custom_partitions = OPTIONS.info_dict.get(
1526 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001527 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 -07001528
Daniel Norman276f0622019-07-26 14:13:51 -07001529 for partition, path in partitions.items():
1530 if partition not in needed_partitions:
1531 continue
1532 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001533 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001534 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001535 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001536 'Unknown partition: {}'.format(partition)
1537 assert os.path.exists(path), \
1538 'Failed to find {} for {}'.format(path, partition)
1539 cmd.extend(GetAvbPartitionArg(partition, path))
1540
1541 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1542 if args and args.strip():
1543 split_args = shlex.split(args)
1544 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001545 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001546 # as a path relative to source tree, which may not be available at the
1547 # same location when running this script (we have the input target_files
1548 # zip only). For such cases, we additionally scan other locations (e.g.
1549 # IMAGES/, RADIO/, etc) before bailing out.
1550 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001551 chained_image = split_args[index + 1]
1552 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001553 continue
1554 found = False
1555 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1556 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001557 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001558 if os.path.exists(alt_path):
1559 split_args[index + 1] = alt_path
1560 found = True
1561 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001562 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001563 cmd.extend(split_args)
1564
1565 RunAndCheckOutput(cmd)
1566
1567
jiajia tang836f76b2021-04-02 14:48:26 +08001568def _MakeRamdisk(sourcedir, fs_config_file=None,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001569 dev_node_file=None,
jiajia tang836f76b2021-04-02 14:48:26 +08001570 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001571 ramdisk_img = tempfile.NamedTemporaryFile()
1572
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001573 cmd = ["mkbootfs"]
1574
1575 if fs_config_file and os.access(fs_config_file, os.F_OK):
1576 cmd.extend(["-f", fs_config_file])
1577
1578 if dev_node_file and os.access(dev_node_file, os.F_OK):
1579 cmd.extend(["-n", dev_node_file])
1580
1581 cmd.append(os.path.join(sourcedir, "RAMDISK"))
1582
Steve Mucklee1b10862019-07-10 10:49:37 -07001583 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001584 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001585 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001586 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001587 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001588 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001589 else:
1590 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001591
1592 p2.wait()
1593 p1.wait()
1594 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001595 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001596
1597 return ramdisk_img
1598
1599
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001600def _BuildBootableImage(image_name, sourcedir, fs_config_file,
1601 dev_node_file=None, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001602 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001603 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001604
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001605 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001606 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1607 we are building a two-step special image (i.e. building a recovery image to
1608 be loaded into /boot in two-step OTAs).
1609
1610 Return the image data, or None if sourcedir does not appear to contains files
1611 for building the requested image.
1612 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001613
Yifan Hong63c5ca12020-10-08 11:54:02 -07001614 if info_dict is None:
1615 info_dict = OPTIONS.info_dict
1616
Steve Muckle9793cf62020-04-08 18:27:00 -07001617 # "boot" or "recovery", without extension.
1618 partition_name = os.path.basename(sourcedir).lower()
1619
Yifan Hong63c5ca12020-10-08 11:54:02 -07001620 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001621 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001622 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1623 logger.info("Excluded kernel binary from recovery image.")
1624 else:
1625 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001626 elif partition_name == "init_boot":
1627 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001628 else:
1629 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001630 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001631 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001632 return None
1633
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001634 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1635
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001636 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001637 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001638
Doug Zongkereef39442009-04-02 12:14:19 -07001639 img = tempfile.NamedTemporaryFile()
1640
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001641 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001642 ramdisk_format = GetRamdiskFormat(info_dict)
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001643 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, dev_node_file,
jiajia tang836f76b2021-04-02 14:48:26 +08001644 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001645
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001646 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1647 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1648
Yifan Hong63c5ca12020-10-08 11:54:02 -07001649 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001650 if kernel_path is not None:
1651 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001652
Benoit Fradina45a8682014-07-14 21:00:43 +02001653 fn = os.path.join(sourcedir, "second")
1654 if os.access(fn, os.F_OK):
1655 cmd.append("--second")
1656 cmd.append(fn)
1657
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001658 fn = os.path.join(sourcedir, "dtb")
1659 if os.access(fn, os.F_OK):
1660 cmd.append("--dtb")
1661 cmd.append(fn)
1662
Doug Zongker171f1cd2009-06-15 22:36:37 -07001663 fn = os.path.join(sourcedir, "cmdline")
1664 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001665 cmd.append("--cmdline")
1666 cmd.append(open(fn).read().rstrip("\n"))
1667
1668 fn = os.path.join(sourcedir, "base")
1669 if os.access(fn, os.F_OK):
1670 cmd.append("--base")
1671 cmd.append(open(fn).read().rstrip("\n"))
1672
Ying Wang4de6b5b2010-08-25 14:29:34 -07001673 fn = os.path.join(sourcedir, "pagesize")
1674 if os.access(fn, os.F_OK):
1675 cmd.append("--pagesize")
1676 cmd.append(open(fn).read().rstrip("\n"))
1677
Steve Mucklef84668e2020-03-16 19:13:46 -07001678 if partition_name == "recovery":
1679 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301680 if not args:
1681 # Fall back to "mkbootimg_args" for recovery image
1682 # in case "recovery_mkbootimg_args" is not set.
1683 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001684 elif partition_name == "init_boot":
1685 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001686 else:
1687 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001688 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001689 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001690
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001691 args = info_dict.get("mkbootimg_version_args")
1692 if args and args.strip():
1693 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001694
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001695 if has_ramdisk:
1696 cmd.extend(["--ramdisk", ramdisk_img.name])
1697
Tao Baod95e9fd2015-03-29 23:07:41 -07001698 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001699 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001700 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001701 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001702 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001703 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001704
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001705 if partition_name == "recovery":
1706 if info_dict.get("include_recovery_dtbo") == "true":
1707 fn = os.path.join(sourcedir, "recovery_dtbo")
1708 cmd.extend(["--recovery_dtbo", fn])
1709 if info_dict.get("include_recovery_acpio") == "true":
1710 fn = os.path.join(sourcedir, "recovery_acpio")
1711 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001712
Tao Bao986ee862018-10-04 15:46:16 -07001713 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001714
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001715 if _HasGkiCertificationArgs():
1716 if not os.path.exists(img.name):
1717 raise ValueError("Cannot find GKI boot.img")
1718 if kernel_path is None or not os.path.exists(kernel_path):
1719 raise ValueError("Cannot find GKI kernel.img")
1720
1721 # Certify GKI images.
1722 boot_signature_bytes = b''
1723 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1724 boot_signature_bytes += _GenerateGkiCertificate(
1725 kernel_path, "generic_kernel")
1726
1727 BOOT_SIGNATURE_SIZE = 16 * 1024
1728 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1729 raise ValueError(
1730 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1731 boot_signature_bytes += (
1732 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1733 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1734
1735 with open(img.name, 'ab') as f:
1736 f.write(boot_signature_bytes)
1737
Tao Baod95e9fd2015-03-29 23:07:41 -07001738 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001739 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001740 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001741 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001742 # We have switched from the prebuilt futility binary to using the tool
1743 # (futility-host) built from the source. Override the setting in the old
1744 # TF.zip.
1745 futility = info_dict["futility"]
1746 if futility.startswith("prebuilts/"):
1747 futility = "futility-host"
1748 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001749 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001750 info_dict["vboot_key"] + ".vbprivk",
1751 info_dict["vboot_subkey"] + ".vbprivk",
1752 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001753 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001754 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001755
Tao Baof3282b42015-04-01 11:21:55 -07001756 # Clean up the temp files.
1757 img_unsigned.close()
1758 img_keyblock.close()
1759
David Zeuthen8fecb282017-12-01 16:24:01 -05001760 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001761 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001762 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001763 if partition_name == "recovery":
1764 part_size = info_dict["recovery_size"]
1765 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001766 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001767 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001768 "--partition_size", str(part_size), "--partition_name",
1769 partition_name]
1770 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001771 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001772 if args and args.strip():
1773 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001774 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001775
1776 img.seek(os.SEEK_SET, 0)
1777 data = img.read()
1778
1779 if has_ramdisk:
1780 ramdisk_img.close()
1781 img.close()
1782
1783 return data
1784
1785
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001786def _SignBootableImage(image_path, prebuilt_name, partition_name,
1787 info_dict=None):
1788 """Performs AVB signing for a prebuilt boot.img.
1789
1790 Args:
1791 image_path: The full path of the image, e.g., /path/to/boot.img.
1792 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001793 boot-5.10.img, recovery.img or init_boot.img.
1794 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001795 info_dict: The information dict read from misc_info.txt.
1796 """
1797 if info_dict is None:
1798 info_dict = OPTIONS.info_dict
1799
1800 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1801 if info_dict.get("avb_enable") == "true":
1802 avbtool = info_dict["avb_avbtool"]
1803 if partition_name == "recovery":
1804 part_size = info_dict["recovery_size"]
1805 else:
1806 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1807
1808 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1809 "--partition_size", str(part_size), "--partition_name",
1810 partition_name]
1811 AppendAVBSigningArgs(cmd, partition_name)
1812 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1813 if args and args.strip():
1814 cmd.extend(shlex.split(args))
1815 RunAndCheckOutput(cmd)
1816
1817
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001818def HasRamdisk(partition_name, info_dict=None):
1819 """Returns true/false to see if a bootable image should have a ramdisk.
1820
1821 Args:
1822 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1823 info_dict: The information dict read from misc_info.txt.
1824 """
1825 if info_dict is None:
1826 info_dict = OPTIONS.info_dict
1827
1828 if partition_name != "boot":
1829 return True # init_boot.img or recovery.img has a ramdisk.
1830
1831 if info_dict.get("recovery_as_boot") == "true":
1832 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1833
Bowgo Tsai85578e02022-04-19 10:50:59 +08001834 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1835 return False # A GKI boot.img has no ramdisk since Android-13.
1836
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001837 if info_dict.get("system_root_image") == "true":
1838 # The ramdisk content is merged into the system.img, so there is NO
1839 # ramdisk in the boot.img or boot-<kernel version>.img.
1840 return False
1841
1842 if info_dict.get("init_boot") == "true":
1843 # The ramdisk is moved to the init_boot.img, so there is NO
1844 # ramdisk in the boot.img or boot-<kernel version>.img.
1845 return False
1846
1847 return True
1848
1849
Doug Zongkerd5131602012-08-02 14:46:42 -07001850def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001851 info_dict=None, two_step_image=False,
1852 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001853 """Return a File object with the desired bootable image.
1854
1855 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1856 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1857 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001858
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001859 if info_dict is None:
1860 info_dict = OPTIONS.info_dict
1861
Doug Zongker55d93282011-01-25 17:03:34 -08001862 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1863 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001864 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001865 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001866
1867 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1868 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001869 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001870 return File.FromLocalFile(name, prebuilt_path)
1871
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001872 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001873 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1874 if os.path.exists(prebuilt_path):
1875 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1876 signed_img = MakeTempFile()
1877 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001878 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1879 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001880
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001881 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001882
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001883 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001884
Doug Zongker6f1d0312014-08-22 08:07:12 -07001885 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001886 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001887 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001888 os.path.join(unpack_dir, 'META/ramdisk_node_list')
1889 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001890 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001891 if data:
1892 return File(name, data)
1893 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001894
Doug Zongkereef39442009-04-02 12:14:19 -07001895
Lucas Wei03230252022-04-18 16:00:40 +08001896def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001897 """Build a vendor boot image from the specified sourcedir.
1898
1899 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1900 turn them into a vendor boot image.
1901
1902 Return the image data, or None if sourcedir does not appear to contains files
1903 for building the requested image.
1904 """
1905
1906 if info_dict is None:
1907 info_dict = OPTIONS.info_dict
1908
1909 img = tempfile.NamedTemporaryFile()
1910
TJ Rhoades6f488e92022-05-01 22:16:22 -07001911 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001912 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001913
1914 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1915 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1916
1917 cmd = [mkbootimg]
1918
1919 fn = os.path.join(sourcedir, "dtb")
1920 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001921 has_vendor_kernel_boot = (info_dict.get(
1922 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001923
1924 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1925 # Otherwise pack dtb into vendor_boot.
1926 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1927 cmd.append("--dtb")
1928 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001929
1930 fn = os.path.join(sourcedir, "vendor_cmdline")
1931 if os.access(fn, os.F_OK):
1932 cmd.append("--vendor_cmdline")
1933 cmd.append(open(fn).read().rstrip("\n"))
1934
1935 fn = os.path.join(sourcedir, "base")
1936 if os.access(fn, os.F_OK):
1937 cmd.append("--base")
1938 cmd.append(open(fn).read().rstrip("\n"))
1939
1940 fn = os.path.join(sourcedir, "pagesize")
1941 if os.access(fn, os.F_OK):
1942 cmd.append("--pagesize")
1943 cmd.append(open(fn).read().rstrip("\n"))
1944
1945 args = info_dict.get("mkbootimg_args")
1946 if args and args.strip():
1947 cmd.extend(shlex.split(args))
1948
1949 args = info_dict.get("mkbootimg_version_args")
1950 if args and args.strip():
1951 cmd.extend(shlex.split(args))
1952
1953 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1954 cmd.extend(["--vendor_boot", img.name])
1955
Devin Moore50509012021-01-13 10:45:04 -08001956 fn = os.path.join(sourcedir, "vendor_bootconfig")
1957 if os.access(fn, os.F_OK):
1958 cmd.append("--vendor_bootconfig")
1959 cmd.append(fn)
1960
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001961 ramdisk_fragment_imgs = []
1962 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1963 if os.access(fn, os.F_OK):
1964 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1965 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001966 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1967 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001968 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001969 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1970 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001971 # Use prebuilt image if found, else create ramdisk from supplied files.
1972 if os.access(fn, os.F_OK):
1973 ramdisk_fragment_pathname = fn
1974 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001975 ramdisk_fragment_root = os.path.join(
1976 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001977 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1978 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001979 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1980 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1981 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1982
Steve Mucklee1b10862019-07-10 10:49:37 -07001983 RunAndCheckOutput(cmd)
1984
1985 # AVB: if enabled, calculate and add hash.
1986 if info_dict.get("avb_enable") == "true":
1987 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001988 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001989 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001990 "--partition_size", str(part_size), "--partition_name", partition_name]
1991 AppendAVBSigningArgs(cmd, partition_name)
1992 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001993 if args and args.strip():
1994 cmd.extend(shlex.split(args))
1995 RunAndCheckOutput(cmd)
1996
1997 img.seek(os.SEEK_SET, 0)
1998 data = img.read()
1999
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002000 for f in ramdisk_fragment_imgs:
2001 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002002 ramdisk_img.close()
2003 img.close()
2004
2005 return data
2006
2007
2008def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2009 info_dict=None):
2010 """Return a File object with the desired vendor boot image.
2011
2012 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2013 the source files in 'unpack_dir'/'tree_subdir'."""
2014
2015 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2016 if os.path.exists(prebuilt_path):
2017 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2018 return File.FromLocalFile(name, prebuilt_path)
2019
2020 logger.info("building image from target_files %s...", tree_subdir)
2021
2022 if info_dict is None:
2023 info_dict = OPTIONS.info_dict
2024
Kelvin Zhang0876c412020-06-23 15:06:58 -04002025 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002026 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2027 if data:
2028 return File(name, data)
2029 return None
2030
2031
2032def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002033 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002034 """Return a File object with the desired vendor kernel boot image.
2035
2036 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2037 the source files in 'unpack_dir'/'tree_subdir'."""
2038
2039 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2040 if os.path.exists(prebuilt_path):
2041 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2042 return File.FromLocalFile(name, prebuilt_path)
2043
2044 logger.info("building image from target_files %s...", tree_subdir)
2045
2046 if info_dict is None:
2047 info_dict = OPTIONS.info_dict
2048
2049 data = _BuildVendorBootImage(
2050 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002051 if data:
2052 return File(name, data)
2053 return None
2054
2055
Narayan Kamatha07bf042017-08-14 14:49:21 +01002056def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002057 """Gunzips the given gzip compressed file to a given output file."""
2058 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002059 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002060 shutil.copyfileobj(in_file, out_file)
2061
2062
Tao Bao0ff15de2019-03-20 11:26:06 -07002063def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002064 """Unzips the archive to the given directory.
2065
2066 Args:
2067 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002068 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002069 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2070 archvie. Non-matching patterns will be filtered out. If there's no match
2071 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002072 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002073 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002074 if patterns is not None:
2075 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002076 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002077 names = input_zip.namelist()
2078 filtered = [
2079 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2080
2081 # There isn't any matching files. Don't unzip anything.
2082 if not filtered:
2083 return
2084 cmd.extend(filtered)
2085
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002086 RunAndCheckOutput(cmd)
2087
2088
Daniel Norman78554ea2021-09-14 10:29:38 -07002089def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002090 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002091
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002092 Args:
2093 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2094 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2095
Daniel Norman78554ea2021-09-14 10:29:38 -07002096 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002097 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002098
Tao Bao1c830bf2017-12-25 10:43:47 -08002099 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002100 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002101 """
Doug Zongkereef39442009-04-02 12:14:19 -07002102
Tao Bao1c830bf2017-12-25 10:43:47 -08002103 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002104 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2105 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002106 UnzipToDir(m.group(1), tmp, patterns)
2107 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002108 filename = m.group(1)
2109 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002110 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002111
Tao Baodba59ee2018-01-09 13:21:02 -08002112 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002113
2114
Yifan Hong8a66a712019-04-04 15:37:57 -07002115def GetUserImage(which, tmpdir, input_zip,
2116 info_dict=None,
2117 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002118 reset_file_map=False):
2119 """Returns an Image object suitable for passing to BlockImageDiff.
2120
2121 This function loads the specified image from the given path. If the specified
2122 image is sparse, it also performs additional processing for OTA purpose. For
2123 example, it always adds block 0 to clobbered blocks list. It also detects
2124 files that cannot be reconstructed from the block list, for whom we should
2125 avoid applying imgdiff.
2126
2127 Args:
2128 which: The partition name.
2129 tmpdir: The directory that contains the prebuilt image and block map file.
2130 input_zip: The target-files ZIP archive.
2131 info_dict: The dict to be looked up for relevant info.
2132 allow_shared_blocks: If image is sparse, whether having shared blocks is
2133 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002134 reset_file_map: If true and image is sparse, reset file map before returning
2135 the image.
2136 Returns:
2137 A Image object. If it is a sparse image and reset_file_map is False, the
2138 image will have file_map info loaded.
2139 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002140 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002141 info_dict = LoadInfoDict(input_zip)
2142
Kelvin Zhang04521282023-03-02 09:42:52 -08002143 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002144
2145 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2146 # shared blocks (i.e. some blocks will show up in multiple files' block
2147 # list). We can only allocate such shared blocks to the first "owner", and
2148 # disable imgdiff for all later occurrences.
2149 if allow_shared_blocks is None:
2150 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2151
2152 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002153 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002154 if reset_file_map:
2155 img.ResetFileMap()
2156 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002157 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002158
2159
hungweichencc9c05d2022-08-23 05:45:42 +00002160def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002161 """Returns a Image object suitable for passing to BlockImageDiff.
2162
2163 This function loads the specified non-sparse image from the given path.
2164
2165 Args:
2166 which: The partition name.
2167 tmpdir: The directory that contains the prebuilt image and block map file.
2168 Returns:
2169 A Image object.
2170 """
2171 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2172 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2173
2174 # The image and map files must have been created prior to calling
2175 # ota_from_target_files.py (since LMP).
2176 assert os.path.exists(path) and os.path.exists(mappath)
2177
hungweichencc9c05d2022-08-23 05:45:42 +00002178 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002179
Yifan Hong8a66a712019-04-04 15:37:57 -07002180
hungweichencc9c05d2022-08-23 05:45:42 +00002181def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002182 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2183
2184 This function loads the specified sparse image from the given path, and
2185 performs additional processing for OTA purpose. For example, it always adds
2186 block 0 to clobbered blocks list. It also detects files that cannot be
2187 reconstructed from the block list, for whom we should avoid applying imgdiff.
2188
2189 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002190 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002191 tmpdir: The directory that contains the prebuilt image and block map file.
2192 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002193 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002194 Returns:
2195 A SparseImage object, with file_map info loaded.
2196 """
Tao Baoc765cca2018-01-31 17:32:40 -08002197 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2198 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2199
2200 # The image and map files must have been created prior to calling
2201 # ota_from_target_files.py (since LMP).
2202 assert os.path.exists(path) and os.path.exists(mappath)
2203
2204 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2205 # it to clobbered_blocks so that it will be written to the target
2206 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2207 clobbered_blocks = "0"
2208
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002209 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002210 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002211
2212 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2213 # if they contain all zeros. We can't reconstruct such a file from its block
2214 # list. Tag such entries accordingly. (Bug: 65213616)
2215 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002216 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002217 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002218 continue
2219
Tom Cherryd14b8952018-08-09 14:26:00 -07002220 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2221 # filename listed in system.map may contain an additional leading slash
2222 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2223 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002224 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002225 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002226 arcname = entry.lstrip('/')
2227 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002228 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002229 else:
2230 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002231
2232 assert arcname in input_zip.namelist(), \
2233 "Failed to find the ZIP entry for {}".format(entry)
2234
Tao Baoc765cca2018-01-31 17:32:40 -08002235 info = input_zip.getinfo(arcname)
2236 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002237
2238 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002239 # image, check the original block list to determine its completeness. Note
2240 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002241 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002242 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002243
Tao Baoc765cca2018-01-31 17:32:40 -08002244 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2245 ranges.extra['incomplete'] = True
2246
2247 return image
2248
2249
Doug Zongkereef39442009-04-02 12:14:19 -07002250def GetKeyPasswords(keylist):
2251 """Given a list of keys, prompt the user to enter passwords for
2252 those which require them. Return a {key: password} dict. password
2253 will be None if the key has no password."""
2254
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255 no_passwords = []
2256 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002257 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002258 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002259
2260 # sorted() can't compare strings to None, so convert Nones to strings
2261 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002262 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002263 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002264 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002265 continue
2266
T.R. Fullhart37e10522013-03-18 10:31:26 -07002267 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002268 "-inform", "DER", "-nocrypt"],
2269 stdin=devnull.fileno(),
2270 stdout=devnull.fileno(),
2271 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002272 p.communicate()
2273 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002274 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002275 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002276 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002277 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2278 "-inform", "DER", "-passin", "pass:"],
2279 stdin=devnull.fileno(),
2280 stdout=devnull.fileno(),
2281 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002282 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002283 if p.returncode == 0:
2284 # Encrypted key with empty string as password.
2285 key_passwords[k] = ''
2286 elif stderr.startswith('Error decrypting key'):
2287 # Definitely encrypted key.
2288 # It would have said "Error reading key" if it didn't parse correctly.
2289 need_passwords.append(k)
2290 else:
2291 # Potentially, a type of key that openssl doesn't understand.
2292 # We'll let the routines in signapk.jar handle it.
2293 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002294 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002295
T.R. Fullhart37e10522013-03-18 10:31:26 -07002296 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002297 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002298 return key_passwords
2299
2300
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002301def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002302 """Gets the minSdkVersion declared in the APK.
2303
Martin Stjernholm58472e82022-01-07 22:08:47 +00002304 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2305 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002306
2307 Args:
2308 apk_name: The APK filename.
2309
2310 Returns:
2311 The parsed SDK version string.
2312
2313 Raises:
2314 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002315 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002316 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002317 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002318 stderr=subprocess.PIPE)
2319 stdoutdata, stderrdata = proc.communicate()
2320 if proc.returncode != 0:
2321 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002322 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2323 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002324
Tao Baof47bf0f2018-03-21 23:28:51 -07002325 for line in stdoutdata.split("\n"):
2326 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002327 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2328 if m:
2329 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002330 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002331
2332
2333def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002334 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002335
Tao Baof47bf0f2018-03-21 23:28:51 -07002336 If minSdkVersion is set to a codename, it is translated to a number using the
2337 provided map.
2338
2339 Args:
2340 apk_name: The APK filename.
2341
2342 Returns:
2343 The parsed SDK version number.
2344
2345 Raises:
2346 ExternalError: On failing to get the min SDK version number.
2347 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002348 version = GetMinSdkVersion(apk_name)
2349 try:
2350 return int(version)
2351 except ValueError:
2352 # Not a decimal number. Codename?
2353 if version in codename_to_api_level_map:
2354 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002355 raise ExternalError(
2356 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2357 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002358
2359
2360def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002361 codename_to_api_level_map=None, whole_file=False,
2362 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002363 """Sign the input_name zip/jar/apk, producing output_name. Use the
2364 given key and password (the latter may be None if the key does not
2365 have a password.
2366
Doug Zongker951495f2009-08-14 12:44:19 -07002367 If whole_file is true, use the "-w" option to SignApk to embed a
2368 signature that covers the whole file in the archive comment of the
2369 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002370
2371 min_api_level is the API Level (int) of the oldest platform this file may end
2372 up on. If not specified for an APK, the API Level is obtained by interpreting
2373 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2374
2375 codename_to_api_level_map is needed to translate the codename which may be
2376 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002377
2378 Caller may optionally specify extra args to be passed to SignApk, which
2379 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002380 """
Tao Bao76def242017-11-21 09:25:31 -08002381 if codename_to_api_level_map is None:
2382 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002383 if extra_signapk_args is None:
2384 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002385
Alex Klyubin9667b182015-12-10 13:38:50 -08002386 java_library_path = os.path.join(
2387 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2388
Tao Baoe95540e2016-11-08 12:08:53 -08002389 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2390 ["-Djava.library.path=" + java_library_path,
2391 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002392 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002393 if whole_file:
2394 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002395
2396 min_sdk_version = min_api_level
2397 if min_sdk_version is None:
2398 if not whole_file:
2399 min_sdk_version = GetMinSdkVersionInt(
2400 input_name, codename_to_api_level_map)
2401 if min_sdk_version is not None:
2402 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2403
T.R. Fullhart37e10522013-03-18 10:31:26 -07002404 cmd.extend([key + OPTIONS.public_key_suffix,
2405 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002406 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002407
Tao Bao73dd4f42018-10-04 16:25:33 -07002408 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002409 if password is not None:
2410 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002411 stdoutdata, _ = proc.communicate(password)
2412 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002413 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002414 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002415 proc.returncode, stdoutdata))
2416
Doug Zongkereef39442009-04-02 12:14:19 -07002417
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002418def SignSePolicy(sepolicy, key, password):
2419 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2420 an RSA .sig signature files.
2421 """
2422
2423 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002424 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002425 return False
2426
2427 java_library_path = os.path.join(
2428 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2429
2430 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002431 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002432 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002433 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002434
2435 cmd.extend([key + OPTIONS.public_key_suffix,
2436 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002437 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002438
2439 proc = Run(cmd, stdin=subprocess.PIPE)
2440 if password is not None:
2441 password += "\n"
2442 stdoutdata, _ = proc.communicate(password)
2443 if proc.returncode != 0:
2444 raise ExternalError(
2445 "Failed to run sign sepolicy: return code {}:\n{}".format(
2446 proc.returncode, stdoutdata))
2447 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002448
Kelvin Zhangf294c872022-10-06 14:21:36 -07002449
Doug Zongker37974732010-09-16 17:44:38 -07002450def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002451 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002452
Tao Bao9dd909e2017-11-14 11:27:32 -08002453 For non-AVB images, raise exception if the data is too big. Print a warning
2454 if the data is nearing the maximum size.
2455
2456 For AVB images, the actual image size should be identical to the limit.
2457
2458 Args:
2459 data: A string that contains all the data for the partition.
2460 target: The partition name. The ".img" suffix is optional.
2461 info_dict: The dict to be looked up for relevant info.
2462 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002463 if target.endswith(".img"):
2464 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002465 mount_point = "/" + target
2466
Ying Wangf8824af2014-06-03 14:07:27 -07002467 fs_type = None
2468 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002469 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002470 if mount_point == "/userdata":
2471 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002472 p = info_dict["fstab"][mount_point]
2473 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002474 device = p.device
2475 if "/" in device:
2476 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002477 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002478 if not fs_type or not limit:
2479 return
Doug Zongkereef39442009-04-02 12:14:19 -07002480
Andrew Boie0f9aec82012-02-14 09:32:52 -08002481 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002482 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2483 # path.
2484 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2485 if size != limit:
2486 raise ExternalError(
2487 "Mismatching image size for %s: expected %d actual %d" % (
2488 target, limit, size))
2489 else:
2490 pct = float(size) * 100.0 / limit
2491 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2492 if pct >= 99.0:
2493 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002494
2495 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002496 logger.warning("\n WARNING: %s\n", msg)
2497 else:
2498 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002499
2500
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002501def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002502 """Parses the APK certs info from a given target-files zip.
2503
2504 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2505 tuple with the following elements: (1) a dictionary that maps packages to
2506 certs (based on the "certificate" and "private_key" attributes in the file;
2507 (2) a string representing the extension of compressed APKs in the target files
2508 (e.g ".gz", ".bro").
2509
2510 Args:
2511 tf_zip: The input target_files ZipFile (already open).
2512
2513 Returns:
2514 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2515 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2516 no compressed APKs.
2517 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002518 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002519 compressed_extension = None
2520
Tao Bao0f990332017-09-08 19:02:54 -07002521 # META/apkcerts.txt contains the info for _all_ the packages known at build
2522 # time. Filter out the ones that are not installed.
2523 installed_files = set()
2524 for name in tf_zip.namelist():
2525 basename = os.path.basename(name)
2526 if basename:
2527 installed_files.add(basename)
2528
Tao Baoda30cfa2017-12-01 16:19:46 -08002529 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002530 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002531 if not line:
2532 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002533 m = re.match(
2534 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002535 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2536 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002537 line)
2538 if not m:
2539 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002540
Tao Bao818ddf52018-01-05 11:17:34 -08002541 matches = m.groupdict()
2542 cert = matches["CERT"]
2543 privkey = matches["PRIVKEY"]
2544 name = matches["NAME"]
2545 this_compressed_extension = matches["COMPRESSED"]
2546
2547 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2548 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2549 if cert in SPECIAL_CERT_STRINGS and not privkey:
2550 certmap[name] = cert
2551 elif (cert.endswith(OPTIONS.public_key_suffix) and
2552 privkey.endswith(OPTIONS.private_key_suffix) and
2553 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2554 certmap[name] = cert[:-public_key_suffix_len]
2555 else:
2556 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2557
2558 if not this_compressed_extension:
2559 continue
2560
2561 # Only count the installed files.
2562 filename = name + '.' + this_compressed_extension
2563 if filename not in installed_files:
2564 continue
2565
2566 # Make sure that all the values in the compression map have the same
2567 # extension. We don't support multiple compression methods in the same
2568 # system image.
2569 if compressed_extension:
2570 if this_compressed_extension != compressed_extension:
2571 raise ValueError(
2572 "Multiple compressed extensions: {} vs {}".format(
2573 compressed_extension, this_compressed_extension))
2574 else:
2575 compressed_extension = this_compressed_extension
2576
2577 return (certmap,
2578 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002579
2580
Doug Zongkereef39442009-04-02 12:14:19 -07002581COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002582Global options
2583
2584 -p (--path) <dir>
2585 Prepend <dir>/bin to the list of places to search for binaries run by this
2586 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002587
Doug Zongker05d3dea2009-06-22 11:32:31 -07002588 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002589 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002590
Tao Bao30df8b42018-04-23 15:32:53 -07002591 -x (--extra) <key=value>
2592 Add a key/value pair to the 'extras' dict, which device-specific extension
2593 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002594
Doug Zongkereef39442009-04-02 12:14:19 -07002595 -v (--verbose)
2596 Show command lines being executed.
2597
2598 -h (--help)
2599 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002600
2601 --logfile <file>
2602 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002603"""
2604
Kelvin Zhang0876c412020-06-23 15:06:58 -04002605
Doug Zongkereef39442009-04-02 12:14:19 -07002606def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002607 print(docstring.rstrip("\n"))
2608 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002609
2610
2611def ParseOptions(argv,
2612 docstring,
2613 extra_opts="", extra_long_opts=(),
2614 extra_option_handler=None):
2615 """Parse the options in argv and return any arguments that aren't
2616 flags. docstring is the calling module's docstring, to be displayed
2617 for errors and -h. extra_opts and extra_long_opts are for flags
2618 defined by the caller, which are processed by passing them to
2619 extra_option_handler."""
2620
2621 try:
2622 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002623 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002624 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002625 "signapk_shared_library_path=", "extra_signapk_args=",
2626 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002627 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002628 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2629 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002630 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002631 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002632 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002633 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002634 sys.exit(2)
2635
Doug Zongkereef39442009-04-02 12:14:19 -07002636 for o, a in opts:
2637 if o in ("-h", "--help"):
2638 Usage(docstring)
2639 sys.exit()
2640 elif o in ("-v", "--verbose"):
2641 OPTIONS.verbose = True
2642 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002643 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002644 elif o in ("--signapk_path",):
2645 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002646 elif o in ("--signapk_shared_library_path",):
2647 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002648 elif o in ("--extra_signapk_args",):
2649 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002650 elif o in ("--sign_sepolicy_path",):
2651 OPTIONS.sign_sepolicy_path = a
2652 elif o in ("--extra_sign_sepolicy_args",):
2653 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002654 elif o in ("--aapt2_path",):
2655 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002656 elif o in ("--java_path",):
2657 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002658 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002659 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002660 elif o in ("--android_jar_path",):
2661 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002662 elif o in ("--public_key_suffix",):
2663 OPTIONS.public_key_suffix = a
2664 elif o in ("--private_key_suffix",):
2665 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002666 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002667 raise ValueError(
2668 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002669 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002670 raise ValueError(
2671 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002672 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002673 raise ValueError(
2674 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002675 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002676 raise ValueError(
2677 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002678 elif o in ("-s", "--device_specific"):
2679 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002680 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002681 key, value = a.split("=", 1)
2682 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002683 elif o in ("--logfile",):
2684 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002685 else:
2686 if extra_option_handler is None or not extra_option_handler(o, a):
2687 assert False, "unknown option \"%s\"" % (o,)
2688
Doug Zongker85448772014-09-09 14:59:20 -07002689 if OPTIONS.search_path:
2690 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2691 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002692
2693 return args
2694
2695
Tao Bao4c851b12016-09-19 13:54:38 -07002696def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002697 """Make a temp file and add it to the list of things to be deleted
2698 when Cleanup() is called. Return the filename."""
2699 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2700 os.close(fd)
2701 OPTIONS.tempfiles.append(fn)
2702 return fn
2703
2704
Tao Bao1c830bf2017-12-25 10:43:47 -08002705def MakeTempDir(prefix='tmp', suffix=''):
2706 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2707
2708 Returns:
2709 The absolute pathname of the new directory.
2710 """
2711 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2712 OPTIONS.tempfiles.append(dir_name)
2713 return dir_name
2714
2715
Doug Zongkereef39442009-04-02 12:14:19 -07002716def Cleanup():
2717 for i in OPTIONS.tempfiles:
2718 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002719 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002720 else:
2721 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002722 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002723
2724
2725class PasswordManager(object):
2726 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002727 self.editor = os.getenv("EDITOR")
2728 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002729
2730 def GetPasswords(self, items):
2731 """Get passwords corresponding to each string in 'items',
2732 returning a dict. (The dict may have keys in addition to the
2733 values in 'items'.)
2734
2735 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2736 user edit that file to add more needed passwords. If no editor is
2737 available, or $ANDROID_PW_FILE isn't define, prompts the user
2738 interactively in the ordinary way.
2739 """
2740
2741 current = self.ReadFile()
2742
2743 first = True
2744 while True:
2745 missing = []
2746 for i in items:
2747 if i not in current or not current[i]:
2748 missing.append(i)
2749 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002750 if not missing:
2751 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002752
2753 for i in missing:
2754 current[i] = ""
2755
2756 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002757 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002758 if sys.version_info[0] >= 3:
2759 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002760 answer = raw_input("try to edit again? [y]> ").strip()
2761 if answer and answer[0] not in 'yY':
2762 raise RuntimeError("key passwords unavailable")
2763 first = False
2764
2765 current = self.UpdateAndReadFile(current)
2766
Kelvin Zhang0876c412020-06-23 15:06:58 -04002767 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002768 """Prompt the user to enter a value (password) for each key in
2769 'current' whose value is fales. Returns a new dict with all the
2770 values.
2771 """
2772 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002773 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002774 if v:
2775 result[k] = v
2776 else:
2777 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002778 result[k] = getpass.getpass(
2779 "Enter password for %s key> " % k).strip()
2780 if result[k]:
2781 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002782 return result
2783
2784 def UpdateAndReadFile(self, current):
2785 if not self.editor or not self.pwfile:
2786 return self.PromptResult(current)
2787
2788 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002789 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002790 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2791 f.write("# (Additional spaces are harmless.)\n\n")
2792
2793 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002794 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002795 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002796 f.write("[[[ %s ]]] %s\n" % (v, k))
2797 if not v and first_line is None:
2798 # position cursor on first line with no password.
2799 first_line = i + 4
2800 f.close()
2801
Tao Bao986ee862018-10-04 15:46:16 -07002802 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002803
2804 return self.ReadFile()
2805
2806 def ReadFile(self):
2807 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002808 if self.pwfile is None:
2809 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002810 try:
2811 f = open(self.pwfile, "r")
2812 for line in f:
2813 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002814 if not line or line[0] == '#':
2815 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002816 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2817 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002818 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002819 else:
2820 result[m.group(2)] = m.group(1)
2821 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002822 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002823 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002824 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002825 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002826
2827
Dan Albert8e0178d2015-01-27 15:53:15 -08002828def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2829 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002830
Dan Albert8e0178d2015-01-27 15:53:15 -08002831 if compress_type is None:
2832 compress_type = zip_file.compression
2833 if arcname is None:
2834 arcname = filename
2835
2836 saved_stat = os.stat(filename)
2837
2838 try:
2839 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2840 # file to be zipped and reset it when we're done.
2841 os.chmod(filename, perms)
2842
2843 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002844 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2845 # intentional. zip stores datetimes in local time without a time zone
2846 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2847 # in the zip archive.
2848 local_epoch = datetime.datetime.fromtimestamp(0)
2849 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002850 os.utime(filename, (timestamp, timestamp))
2851
2852 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2853 finally:
2854 os.chmod(filename, saved_stat.st_mode)
2855 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Dan Albert8e0178d2015-01-27 15:53:15 -08002856
2857
Tao Bao58c1b962015-05-20 09:32:18 -07002858def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002859 compress_type=None):
2860 """Wrap zipfile.writestr() function to work around the zip64 limit.
2861
Kelvin Zhang37a42902022-10-26 12:49:03 -07002862 Python's zip implementation won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07002863 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2864 when calling crc32(bytes).
2865
2866 But it still works fine to write a shorter string into a large zip file.
2867 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2868 when we know the string won't be too long.
2869 """
2870
Tao Baof3282b42015-04-01 11:21:55 -07002871 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2872 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002873 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002874 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002875 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002876 else:
Tao Baof3282b42015-04-01 11:21:55 -07002877 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002878 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2879 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2880 # such a case (since
2881 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2882 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2883 # permission bits. We follow the logic in Python 3 to get consistent
2884 # behavior between using the two versions.
2885 if not zinfo.external_attr:
2886 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002887
2888 # If compress_type is given, it overrides the value in zinfo.
2889 if compress_type is not None:
2890 zinfo.compress_type = compress_type
2891
Tao Bao58c1b962015-05-20 09:32:18 -07002892 # If perms is given, it has a priority.
2893 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002894 # If perms doesn't set the file type, mark it as a regular file.
2895 if perms & 0o770000 == 0:
2896 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002897 zinfo.external_attr = perms << 16
2898
Tao Baof3282b42015-04-01 11:21:55 -07002899 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002900 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2901
Dan Albert8b72aef2015-03-23 19:13:21 -07002902 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002903
2904
Kelvin Zhang1caead02022-09-23 10:06:03 -07002905def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08002906 """Deletes entries from a ZIP file.
2907
Tao Bao89d7ab22017-12-14 17:05:33 -08002908 Args:
2909 zip_filename: The name of the ZIP file.
2910 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08002911 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002912 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002913 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002914 # If list is empty, nothing to do
2915 if not entries:
2916 return
Wei Li8895f9e2022-10-10 17:13:17 -07002917
2918 with zipfile.ZipFile(zip_filename, 'r') as zin:
2919 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
2920 raise ExternalError(
2921 "Failed to delete zip entries, name not matched: %s" % entries)
2922
2923 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
2924 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08002925 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
2926 for entry in entries:
2927 cmd.append("-x")
2928 cmd.append(entry)
2929 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07002930
Wei Li8895f9e2022-10-10 17:13:17 -07002931
2932 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08002933
2934
Doug Zongker05d3dea2009-06-22 11:32:31 -07002935class DeviceSpecificParams(object):
2936 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002937
Doug Zongker05d3dea2009-06-22 11:32:31 -07002938 def __init__(self, **kwargs):
2939 """Keyword arguments to the constructor become attributes of this
2940 object, which is passed to all functions in the device-specific
2941 module."""
Tao Bao38884282019-07-10 22:20:56 -07002942 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002943 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002944 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002945
2946 if self.module is None:
2947 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002948 if not path:
2949 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002950 try:
2951 if os.path.isdir(path):
2952 info = imp.find_module("releasetools", [path])
2953 else:
2954 d, f = os.path.split(path)
2955 b, x = os.path.splitext(f)
2956 if x == ".py":
2957 f = b
2958 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002959 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002960 self.module = imp.load_module("device_specific", *info)
2961 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002962 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002963
2964 def _DoCall(self, function_name, *args, **kwargs):
2965 """Call the named function in the device-specific module, passing
2966 the given args and kwargs. The first argument to the call will be
2967 the DeviceSpecific object itself. If there is no module, or the
2968 module does not define the function, return the value of the
2969 'default' kwarg (which itself defaults to None)."""
2970 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002971 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002972 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2973
2974 def FullOTA_Assertions(self):
2975 """Called after emitting the block of assertions at the top of a
2976 full OTA package. Implementations can add whatever additional
2977 assertions they like."""
2978 return self._DoCall("FullOTA_Assertions")
2979
Doug Zongkere5ff5902012-01-17 10:55:37 -08002980 def FullOTA_InstallBegin(self):
2981 """Called at the start of full OTA installation."""
2982 return self._DoCall("FullOTA_InstallBegin")
2983
Yifan Hong10c530d2018-12-27 17:34:18 -08002984 def FullOTA_GetBlockDifferences(self):
2985 """Called during full OTA installation and verification.
2986 Implementation should return a list of BlockDifference objects describing
2987 the update on each additional partitions.
2988 """
2989 return self._DoCall("FullOTA_GetBlockDifferences")
2990
Doug Zongker05d3dea2009-06-22 11:32:31 -07002991 def FullOTA_InstallEnd(self):
2992 """Called at the end of full OTA installation; typically this is
2993 used to install the image for the device's baseband processor."""
2994 return self._DoCall("FullOTA_InstallEnd")
2995
2996 def IncrementalOTA_Assertions(self):
2997 """Called after emitting the block of assertions at the top of an
2998 incremental OTA package. Implementations can add whatever
2999 additional assertions they like."""
3000 return self._DoCall("IncrementalOTA_Assertions")
3001
Doug Zongkere5ff5902012-01-17 10:55:37 -08003002 def IncrementalOTA_VerifyBegin(self):
3003 """Called at the start of the verification phase of incremental
3004 OTA installation; additional checks can be placed here to abort
3005 the script before any changes are made."""
3006 return self._DoCall("IncrementalOTA_VerifyBegin")
3007
Doug Zongker05d3dea2009-06-22 11:32:31 -07003008 def IncrementalOTA_VerifyEnd(self):
3009 """Called at the end of the verification phase of incremental OTA
3010 installation; additional checks can be placed here to abort the
3011 script before any changes are made."""
3012 return self._DoCall("IncrementalOTA_VerifyEnd")
3013
Doug Zongkere5ff5902012-01-17 10:55:37 -08003014 def IncrementalOTA_InstallBegin(self):
3015 """Called at the start of incremental OTA installation (after
3016 verification is complete)."""
3017 return self._DoCall("IncrementalOTA_InstallBegin")
3018
Yifan Hong10c530d2018-12-27 17:34:18 -08003019 def IncrementalOTA_GetBlockDifferences(self):
3020 """Called during incremental OTA installation and verification.
3021 Implementation should return a list of BlockDifference objects describing
3022 the update on each additional partitions.
3023 """
3024 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3025
Doug Zongker05d3dea2009-06-22 11:32:31 -07003026 def IncrementalOTA_InstallEnd(self):
3027 """Called at the end of incremental OTA installation; typically
3028 this is used to install the image for the device's baseband
3029 processor."""
3030 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003031
Tao Bao9bc6bb22015-11-09 16:58:28 -08003032 def VerifyOTA_Assertions(self):
3033 return self._DoCall("VerifyOTA_Assertions")
3034
Tao Bao76def242017-11-21 09:25:31 -08003035
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003036class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003037 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003038 self.name = name
3039 self.data = data
3040 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003041 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003042 self.sha1 = sha1(data).hexdigest()
3043
3044 @classmethod
3045 def FromLocalFile(cls, name, diskname):
3046 f = open(diskname, "rb")
3047 data = f.read()
3048 f.close()
3049 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003050
3051 def WriteToTemp(self):
3052 t = tempfile.NamedTemporaryFile()
3053 t.write(self.data)
3054 t.flush()
3055 return t
3056
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003057 def WriteToDir(self, d):
3058 with open(os.path.join(d, self.name), "wb") as fp:
3059 fp.write(self.data)
3060
Geremy Condra36bd3652014-02-06 19:45:10 -08003061 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003062 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003063
Tao Bao76def242017-11-21 09:25:31 -08003064
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003065DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003066 ".gz": "imgdiff",
3067 ".zip": ["imgdiff", "-z"],
3068 ".jar": ["imgdiff", "-z"],
3069 ".apk": ["imgdiff", "-z"],
3070 ".img": "imgdiff",
3071}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003072
Tao Bao76def242017-11-21 09:25:31 -08003073
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003074class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003075 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003076 self.tf = tf
3077 self.sf = sf
3078 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003079 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003080
3081 def ComputePatch(self):
3082 """Compute the patch (as a string of data) needed to turn sf into
3083 tf. Returns the same tuple as GetPatch()."""
3084
3085 tf = self.tf
3086 sf = self.sf
3087
Doug Zongker24cd2802012-08-14 16:36:15 -07003088 if self.diff_program:
3089 diff_program = self.diff_program
3090 else:
3091 ext = os.path.splitext(tf.name)[1]
3092 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003093
3094 ttemp = tf.WriteToTemp()
3095 stemp = sf.WriteToTemp()
3096
3097 ext = os.path.splitext(tf.name)[1]
3098
3099 try:
3100 ptemp = tempfile.NamedTemporaryFile()
3101 if isinstance(diff_program, list):
3102 cmd = copy.copy(diff_program)
3103 else:
3104 cmd = [diff_program]
3105 cmd.append(stemp.name)
3106 cmd.append(ttemp.name)
3107 cmd.append(ptemp.name)
3108 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003109 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003110
Doug Zongkerf8340082014-08-05 10:39:37 -07003111 def run():
3112 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003113 if e:
3114 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003115 th = threading.Thread(target=run)
3116 th.start()
3117 th.join(timeout=300) # 5 mins
3118 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003119 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003120 p.terminate()
3121 th.join(5)
3122 if th.is_alive():
3123 p.kill()
3124 th.join()
3125
Tianjie Xua2a9f992018-01-05 15:15:54 -08003126 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003127 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003128 self.patch = None
3129 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003130 diff = ptemp.read()
3131 finally:
3132 ptemp.close()
3133 stemp.close()
3134 ttemp.close()
3135
3136 self.patch = diff
3137 return self.tf, self.sf, self.patch
3138
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003139 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003140 """Returns a tuple of (target_file, source_file, patch_data).
3141
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003142 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003143 computing the patch failed.
3144 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003145 return self.tf, self.sf, self.patch
3146
3147
3148def ComputeDifferences(diffs):
3149 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003150 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003151
3152 # Do the largest files first, to try and reduce the long-pole effect.
3153 by_size = [(i.tf.size, i) for i in diffs]
3154 by_size.sort(reverse=True)
3155 by_size = [i[1] for i in by_size]
3156
3157 lock = threading.Lock()
3158 diff_iter = iter(by_size) # accessed under lock
3159
3160 def worker():
3161 try:
3162 lock.acquire()
3163 for d in diff_iter:
3164 lock.release()
3165 start = time.time()
3166 d.ComputePatch()
3167 dur = time.time() - start
3168 lock.acquire()
3169
3170 tf, sf, patch = d.GetPatch()
3171 if sf.name == tf.name:
3172 name = tf.name
3173 else:
3174 name = "%s (%s)" % (tf.name, sf.name)
3175 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003176 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003177 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003178 logger.info(
3179 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3180 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003181 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003182 except Exception:
3183 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003184 raise
3185
3186 # start worker threads; wait for them all to finish.
3187 threads = [threading.Thread(target=worker)
3188 for i in range(OPTIONS.worker_threads)]
3189 for th in threads:
3190 th.start()
3191 while threads:
3192 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003193
3194
Dan Albert8b72aef2015-03-23 19:13:21 -07003195class BlockDifference(object):
3196 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003197 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003198 self.tgt = tgt
3199 self.src = src
3200 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003201 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003202 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003203
Tao Baodd2a5892015-03-12 12:32:37 -07003204 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003205 version = max(
3206 int(i) for i in
3207 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003208 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003209 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003210
Tianjie Xu41976c72019-07-03 13:57:01 -07003211 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3212 version=self.version,
3213 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003214 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003215 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003216 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003217 self.touched_src_ranges = b.touched_src_ranges
3218 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003219
Yifan Hong10c530d2018-12-27 17:34:18 -08003220 # On devices with dynamic partitions, for new partitions,
3221 # src is None but OPTIONS.source_info_dict is not.
3222 if OPTIONS.source_info_dict is None:
3223 is_dynamic_build = OPTIONS.info_dict.get(
3224 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003225 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003226 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003227 is_dynamic_build = OPTIONS.source_info_dict.get(
3228 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003229 is_dynamic_source = partition in shlex.split(
3230 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003231
Yifan Hongbb2658d2019-01-25 12:30:58 -08003232 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003233 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3234
Yifan Hongbb2658d2019-01-25 12:30:58 -08003235 # For dynamic partitions builds, check partition list in both source
3236 # and target build because new partitions may be added, and existing
3237 # partitions may be removed.
3238 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3239
Yifan Hong10c530d2018-12-27 17:34:18 -08003240 if is_dynamic:
3241 self.device = 'map_partition("%s")' % partition
3242 else:
3243 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003244 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3245 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003246 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003247 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3248 OPTIONS.source_info_dict)
3249 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003250
Tao Baod8d14be2016-02-04 14:26:02 -08003251 @property
3252 def required_cache(self):
3253 return self._required_cache
3254
Tao Bao76def242017-11-21 09:25:31 -08003255 def WriteScript(self, script, output_zip, progress=None,
3256 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003257 if not self.src:
3258 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003259 script.Print("Patching %s image unconditionally..." % (self.partition,))
3260 else:
3261 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003262
Dan Albert8b72aef2015-03-23 19:13:21 -07003263 if progress:
3264 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003265 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003266
3267 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003268 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003269
Tao Bao9bc6bb22015-11-09 16:58:28 -08003270 def WriteStrictVerifyScript(self, script):
3271 """Verify all the blocks in the care_map, including clobbered blocks.
3272
3273 This differs from the WriteVerifyScript() function: a) it prints different
3274 error messages; b) it doesn't allow half-way updated images to pass the
3275 verification."""
3276
3277 partition = self.partition
3278 script.Print("Verifying %s..." % (partition,))
3279 ranges = self.tgt.care_map
3280 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003281 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003282 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3283 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003284 self.device, ranges_str,
3285 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003286 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003287 script.AppendExtra("")
3288
Tao Baod522bdc2016-04-12 15:53:16 -07003289 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003290 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003291
3292 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003293 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003294 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003295
3296 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003297 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003298 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003299 ranges = self.touched_src_ranges
3300 expected_sha1 = self.touched_src_sha1
3301 else:
3302 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3303 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003304
3305 # No blocks to be checked, skipping.
3306 if not ranges:
3307 return
3308
Tao Bao5ece99d2015-05-12 11:42:31 -07003309 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003310 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003311 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003312 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3313 '"%s.patch.dat")) then' % (
3314 self.device, ranges_str, expected_sha1,
3315 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003316 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003317 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003318
Tianjie Xufc3422a2015-12-15 11:53:59 -08003319 if self.version >= 4:
3320
3321 # Bug: 21124327
3322 # When generating incrementals for the system and vendor partitions in
3323 # version 4 or newer, explicitly check the first block (which contains
3324 # the superblock) of the partition to see if it's what we expect. If
3325 # this check fails, give an explicit log message about the partition
3326 # having been remounted R/W (the most likely explanation).
3327 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003328 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003329
3330 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003331 if partition == "system":
3332 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3333 else:
3334 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003335 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003336 'ifelse (block_image_recover({device}, "{ranges}") && '
3337 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003338 'package_extract_file("{partition}.transfer.list"), '
3339 '"{partition}.new.dat", "{partition}.patch.dat"), '
3340 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003341 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003342 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003343 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003344
Tao Baodd2a5892015-03-12 12:32:37 -07003345 # Abort the OTA update. Note that the incremental OTA cannot be applied
3346 # even if it may match the checksum of the target partition.
3347 # a) If version < 3, operations like move and erase will make changes
3348 # unconditionally and damage the partition.
3349 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003350 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003351 if partition == "system":
3352 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3353 else:
3354 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3355 script.AppendExtra((
3356 'abort("E%d: %s partition has unexpected contents");\n'
3357 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003358
Yifan Hong10c530d2018-12-27 17:34:18 -08003359 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003360 partition = self.partition
3361 script.Print('Verifying the updated %s image...' % (partition,))
3362 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3363 ranges = self.tgt.care_map
3364 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003365 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003366 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003367 self.device, ranges_str,
3368 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003369
3370 # Bug: 20881595
3371 # Verify that extended blocks are really zeroed out.
3372 if self.tgt.extended:
3373 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003374 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003375 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003376 self.device, ranges_str,
3377 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003378 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003379 if partition == "system":
3380 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3381 else:
3382 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003383 script.AppendExtra(
3384 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003385 ' abort("E%d: %s partition has unexpected non-zero contents after '
3386 'OTA update");\n'
3387 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003388 else:
3389 script.Print('Verified the updated %s image.' % (partition,))
3390
Tianjie Xu209db462016-05-24 17:34:52 -07003391 if partition == "system":
3392 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3393 else:
3394 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3395
Tao Bao5fcaaef2015-06-01 13:40:49 -07003396 script.AppendExtra(
3397 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003398 ' abort("E%d: %s partition has unexpected contents after OTA '
3399 'update");\n'
3400 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003401
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003402 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003403 ZipWrite(output_zip,
3404 '{}.transfer.list'.format(self.path),
3405 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003406
Tao Bao76def242017-11-21 09:25:31 -08003407 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3408 # its size. Quailty 9 almost triples the compression time but doesn't
3409 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003410 # zip | brotli(quality 6) | brotli(quality 9)
3411 # compressed_size: 942M | 869M (~8% reduced) | 854M
3412 # compression_time: 75s | 265s | 719s
3413 # decompression_time: 15s | 25s | 25s
3414
3415 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003416 brotli_cmd = ['brotli', '--quality=6',
3417 '--output={}.new.dat.br'.format(self.path),
3418 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003419 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003420 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003421
3422 new_data_name = '{}.new.dat.br'.format(self.partition)
3423 ZipWrite(output_zip,
3424 '{}.new.dat.br'.format(self.path),
3425 new_data_name,
3426 compress_type=zipfile.ZIP_STORED)
3427 else:
3428 new_data_name = '{}.new.dat'.format(self.partition)
3429 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3430
Dan Albert8e0178d2015-01-27 15:53:15 -08003431 ZipWrite(output_zip,
3432 '{}.patch.dat'.format(self.path),
3433 '{}.patch.dat'.format(self.partition),
3434 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003435
Tianjie Xu209db462016-05-24 17:34:52 -07003436 if self.partition == "system":
3437 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3438 else:
3439 code = ErrorCode.VENDOR_UPDATE_FAILURE
3440
Yifan Hong10c530d2018-12-27 17:34:18 -08003441 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003442 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003443 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003444 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003445 device=self.device, partition=self.partition,
3446 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003447 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003448
Kelvin Zhang0876c412020-06-23 15:06:58 -04003449 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003450 data = source.ReadRangeSet(ranges)
3451 ctx = sha1()
3452
3453 for p in data:
3454 ctx.update(p)
3455
3456 return ctx.hexdigest()
3457
Kelvin Zhang0876c412020-06-23 15:06:58 -04003458 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003459 """Return the hash value for all zero blocks."""
3460 zero_block = '\x00' * 4096
3461 ctx = sha1()
3462 for _ in range(num_blocks):
3463 ctx.update(zero_block)
3464
3465 return ctx.hexdigest()
3466
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003467
Tianjie Xu41976c72019-07-03 13:57:01 -07003468# Expose these two classes to support vendor-specific scripts
3469DataImage = images.DataImage
3470EmptyImage = images.EmptyImage
3471
Tao Bao76def242017-11-21 09:25:31 -08003472
Doug Zongker96a57e72010-09-26 14:57:41 -07003473# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003474PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003475 "ext4": "EMMC",
3476 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003477 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003478 "squashfs": "EMMC",
3479 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003480}
Doug Zongker96a57e72010-09-26 14:57:41 -07003481
Kelvin Zhang0876c412020-06-23 15:06:58 -04003482
Yifan Hongbdb32012020-05-07 12:38:53 -07003483def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3484 """
3485 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3486 backwards compatibility. It aborts if the fstab entry has slotselect option
3487 (unless check_no_slot is explicitly set to False).
3488 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003489 fstab = info["fstab"]
3490 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003491 if check_no_slot:
3492 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003493 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003494 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3495 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003496 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003497
3498
Yifan Hongbdb32012020-05-07 12:38:53 -07003499def GetTypeAndDeviceExpr(mount_point, info):
3500 """
3501 Return the filesystem of the partition, and an edify expression that evaluates
3502 to the device at runtime.
3503 """
3504 fstab = info["fstab"]
3505 if fstab:
3506 p = fstab[mount_point]
3507 device_expr = '"%s"' % fstab[mount_point].device
3508 if p.slotselect:
3509 device_expr = 'add_slot_suffix(%s)' % device_expr
3510 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003511 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003512
3513
3514def GetEntryForDevice(fstab, device):
3515 """
3516 Returns:
3517 The first entry in fstab whose device is the given value.
3518 """
3519 if not fstab:
3520 return None
3521 for mount_point in fstab:
3522 if fstab[mount_point].device == device:
3523 return fstab[mount_point]
3524 return None
3525
Kelvin Zhang0876c412020-06-23 15:06:58 -04003526
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003527def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003528 """Parses and converts a PEM-encoded certificate into DER-encoded.
3529
3530 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3531
3532 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003533 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003534 """
3535 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003536 save = False
3537 for line in data.split("\n"):
3538 if "--END CERTIFICATE--" in line:
3539 break
3540 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003541 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003542 if "--BEGIN CERTIFICATE--" in line:
3543 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003544 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003545 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003546
Tao Bao04e1f012018-02-04 12:13:35 -08003547
3548def ExtractPublicKey(cert):
3549 """Extracts the public key (PEM-encoded) from the given certificate file.
3550
3551 Args:
3552 cert: The certificate filename.
3553
3554 Returns:
3555 The public key string.
3556
3557 Raises:
3558 AssertionError: On non-zero return from 'openssl'.
3559 """
3560 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3561 # While openssl 1.1 writes the key into the given filename followed by '-out',
3562 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3563 # stdout instead.
3564 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3565 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3566 pubkey, stderrdata = proc.communicate()
3567 assert proc.returncode == 0, \
3568 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3569 return pubkey
3570
3571
Tao Bao1ac886e2019-06-26 11:58:22 -07003572def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003573 """Extracts the AVB public key from the given public or private key.
3574
3575 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003576 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003577 key: The input key file, which should be PEM-encoded public or private key.
3578
3579 Returns:
3580 The path to the extracted AVB public key file.
3581 """
3582 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3583 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003584 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003585 return output
3586
3587
Doug Zongker412c02f2014-02-13 10:58:24 -08003588def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3589 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003590 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003591
Tao Bao6d5d6232018-03-09 17:04:42 -08003592 Most of the space in the boot and recovery images is just the kernel, which is
3593 identical for the two, so the resulting patch should be efficient. Add it to
3594 the output zip, along with a shell script that is run from init.rc on first
3595 boot to actually do the patching and install the new recovery image.
3596
3597 Args:
3598 input_dir: The top-level input directory of the target-files.zip.
3599 output_sink: The callback function that writes the result.
3600 recovery_img: File object for the recovery image.
3601 boot_img: File objects for the boot image.
3602 info_dict: A dict returned by common.LoadInfoDict() on the input
3603 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003604 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003605 if info_dict is None:
3606 info_dict = OPTIONS.info_dict
3607
Tao Bao6d5d6232018-03-09 17:04:42 -08003608 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003609 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3610
3611 if board_uses_vendorimage:
3612 # In this case, the output sink is rooted at VENDOR
3613 recovery_img_path = "etc/recovery.img"
3614 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3615 sh_dir = "bin"
3616 else:
3617 # In this case the output sink is rooted at SYSTEM
3618 recovery_img_path = "vendor/etc/recovery.img"
3619 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3620 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003621
Tao Baof2cffbd2015-07-22 12:33:18 -07003622 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003623 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003624
3625 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003626 system_root_image = info_dict.get("system_root_image") == "true"
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003627 include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
3628 include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003629 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003630 # With system-root-image, boot and recovery images will have mismatching
3631 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3632 # to handle such a case.
Oleg Lyovin6d75a852023-03-22 17:50:02 +03003633 if system_root_image or include_recovery_dtbo or include_recovery_acpio:
Tao Bao6d5d6232018-03-09 17:04:42 -08003634 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003635 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003636 assert not os.path.exists(path)
3637 else:
3638 diff_program = ["imgdiff"]
3639 if os.path.exists(path):
3640 diff_program.append("-b")
3641 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003642 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003643 else:
3644 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003645
3646 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3647 _, _, patch = d.ComputePatch()
3648 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003649
Dan Albertebb19aa2015-03-27 19:11:53 -07003650 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003651 # The following GetTypeAndDevice()s need to use the path in the target
3652 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003653 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3654 check_no_slot=False)
3655 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3656 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003657 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003658 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003659
Tao Baof2cffbd2015-07-22 12:33:18 -07003660 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003661
3662 # Note that we use /vendor to refer to the recovery resources. This will
3663 # work for a separate vendor partition mounted at /vendor or a
3664 # /system/vendor subdirectory on the system partition, for which init will
3665 # create a symlink from /vendor to /system/vendor.
3666
3667 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003668if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3669 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003670 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003671 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3672 log -t recovery "Installing new recovery image: succeeded" || \\
3673 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003674else
3675 log -t recovery "Recovery image already installed"
3676fi
3677""" % {'type': recovery_type,
3678 'device': recovery_device,
3679 'sha1': recovery_img.sha1,
3680 'size': recovery_img.size}
3681 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003682 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003683if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3684 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003685 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003686 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3687 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3688 log -t recovery "Installing new recovery image: succeeded" || \\
3689 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003690else
3691 log -t recovery "Recovery image already installed"
3692fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003693""" % {'boot_size': boot_img.size,
3694 'boot_sha1': boot_img.sha1,
3695 'recovery_size': recovery_img.size,
3696 'recovery_sha1': recovery_img.sha1,
3697 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003698 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003699 'recovery_type': recovery_type,
3700 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003701 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003702
Bill Peckhame868aec2019-09-17 17:06:47 -07003703 # The install script location moved from /system/etc to /system/bin in the L
3704 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3705 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003706
Tao Bao32fcdab2018-10-12 10:30:39 -07003707 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003708
Tao Baoda30cfa2017-12-01 16:19:46 -08003709 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003710
3711
3712class DynamicPartitionUpdate(object):
3713 def __init__(self, src_group=None, tgt_group=None, progress=None,
3714 block_difference=None):
3715 self.src_group = src_group
3716 self.tgt_group = tgt_group
3717 self.progress = progress
3718 self.block_difference = block_difference
3719
3720 @property
3721 def src_size(self):
3722 if not self.block_difference:
3723 return 0
3724 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3725
3726 @property
3727 def tgt_size(self):
3728 if not self.block_difference:
3729 return 0
3730 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3731
3732 @staticmethod
3733 def _GetSparseImageSize(img):
3734 if not img:
3735 return 0
3736 return img.blocksize * img.total_blocks
3737
3738
3739class DynamicGroupUpdate(object):
3740 def __init__(self, src_size=None, tgt_size=None):
3741 # None: group does not exist. 0: no size limits.
3742 self.src_size = src_size
3743 self.tgt_size = tgt_size
3744
3745
3746class DynamicPartitionsDifference(object):
3747 def __init__(self, info_dict, block_diffs, progress_dict=None,
3748 source_info_dict=None):
3749 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003750 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003751
3752 self._remove_all_before_apply = False
3753 if source_info_dict is None:
3754 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003755 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003756
Tao Baof1113e92019-06-18 12:10:14 -07003757 block_diff_dict = collections.OrderedDict(
3758 [(e.partition, e) for e in block_diffs])
3759
Yifan Hong10c530d2018-12-27 17:34:18 -08003760 assert len(block_diff_dict) == len(block_diffs), \
3761 "Duplicated BlockDifference object for {}".format(
3762 [partition for partition, count in
3763 collections.Counter(e.partition for e in block_diffs).items()
3764 if count > 1])
3765
Yifan Hong79997e52019-01-23 16:56:19 -08003766 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003767
3768 for p, block_diff in block_diff_dict.items():
3769 self._partition_updates[p] = DynamicPartitionUpdate()
3770 self._partition_updates[p].block_difference = block_diff
3771
3772 for p, progress in progress_dict.items():
3773 if p in self._partition_updates:
3774 self._partition_updates[p].progress = progress
3775
3776 tgt_groups = shlex.split(info_dict.get(
3777 "super_partition_groups", "").strip())
3778 src_groups = shlex.split(source_info_dict.get(
3779 "super_partition_groups", "").strip())
3780
3781 for g in tgt_groups:
3782 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003783 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003784 assert p in self._partition_updates, \
3785 "{} is in target super_{}_partition_list but no BlockDifference " \
3786 "object is provided.".format(p, g)
3787 self._partition_updates[p].tgt_group = g
3788
3789 for g in src_groups:
3790 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003791 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003792 assert p in self._partition_updates, \
3793 "{} is in source super_{}_partition_list but no BlockDifference " \
3794 "object is provided.".format(p, g)
3795 self._partition_updates[p].src_group = g
3796
Yifan Hong45433e42019-01-18 13:55:25 -08003797 target_dynamic_partitions = set(shlex.split(info_dict.get(
3798 "dynamic_partition_list", "").strip()))
3799 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3800 if u.tgt_size)
3801 assert block_diffs_with_target == target_dynamic_partitions, \
3802 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3803 list(target_dynamic_partitions), list(block_diffs_with_target))
3804
3805 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3806 "dynamic_partition_list", "").strip()))
3807 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3808 if u.src_size)
3809 assert block_diffs_with_source == source_dynamic_partitions, \
3810 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3811 list(source_dynamic_partitions), list(block_diffs_with_source))
3812
Yifan Hong10c530d2018-12-27 17:34:18 -08003813 if self._partition_updates:
3814 logger.info("Updating dynamic partitions %s",
3815 self._partition_updates.keys())
3816
Yifan Hong79997e52019-01-23 16:56:19 -08003817 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003818
3819 for g in tgt_groups:
3820 self._group_updates[g] = DynamicGroupUpdate()
3821 self._group_updates[g].tgt_size = int(info_dict.get(
3822 "super_%s_group_size" % g, "0").strip())
3823
3824 for g in src_groups:
3825 if g not in self._group_updates:
3826 self._group_updates[g] = DynamicGroupUpdate()
3827 self._group_updates[g].src_size = int(source_info_dict.get(
3828 "super_%s_group_size" % g, "0").strip())
3829
3830 self._Compute()
3831
3832 def WriteScript(self, script, output_zip, write_verify_script=False):
3833 script.Comment('--- Start patching dynamic partitions ---')
3834 for p, u in self._partition_updates.items():
3835 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3836 script.Comment('Patch partition %s' % p)
3837 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3838 write_verify_script=False)
3839
3840 op_list_path = MakeTempFile()
3841 with open(op_list_path, 'w') as f:
3842 for line in self._op_list:
3843 f.write('{}\n'.format(line))
3844
3845 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3846
3847 script.Comment('Update dynamic partition metadata')
3848 script.AppendExtra('assert(update_dynamic_partitions('
3849 'package_extract_file("dynamic_partitions_op_list")));')
3850
3851 if write_verify_script:
3852 for p, u in self._partition_updates.items():
3853 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3854 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003855 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003856
3857 for p, u in self._partition_updates.items():
3858 if u.tgt_size and u.src_size <= u.tgt_size:
3859 script.Comment('Patch partition %s' % p)
3860 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3861 write_verify_script=write_verify_script)
3862 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003863 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003864
3865 script.Comment('--- End patching dynamic partitions ---')
3866
3867 def _Compute(self):
3868 self._op_list = list()
3869
3870 def append(line):
3871 self._op_list.append(line)
3872
3873 def comment(line):
3874 self._op_list.append("# %s" % line)
3875
3876 if self._remove_all_before_apply:
3877 comment('Remove all existing dynamic partitions and groups before '
3878 'applying full OTA')
3879 append('remove_all_groups')
3880
3881 for p, u in self._partition_updates.items():
3882 if u.src_group and not u.tgt_group:
3883 append('remove %s' % p)
3884
3885 for p, u in self._partition_updates.items():
3886 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3887 comment('Move partition %s from %s to default' % (p, u.src_group))
3888 append('move %s default' % p)
3889
3890 for p, u in self._partition_updates.items():
3891 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3892 comment('Shrink partition %s from %d to %d' %
3893 (p, u.src_size, u.tgt_size))
3894 append('resize %s %s' % (p, u.tgt_size))
3895
3896 for g, u in self._group_updates.items():
3897 if u.src_size is not None and u.tgt_size is None:
3898 append('remove_group %s' % g)
3899 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003900 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003901 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3902 append('resize_group %s %d' % (g, u.tgt_size))
3903
3904 for g, u in self._group_updates.items():
3905 if u.src_size is None and u.tgt_size is not None:
3906 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3907 append('add_group %s %d' % (g, u.tgt_size))
3908 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003909 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003910 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3911 append('resize_group %s %d' % (g, u.tgt_size))
3912
3913 for p, u in self._partition_updates.items():
3914 if u.tgt_group and not u.src_group:
3915 comment('Add partition %s to group %s' % (p, u.tgt_group))
3916 append('add %s %s' % (p, u.tgt_group))
3917
3918 for p, u in self._partition_updates.items():
3919 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003920 comment('Grow partition %s from %d to %d' %
3921 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003922 append('resize %s %d' % (p, u.tgt_size))
3923
3924 for p, u in self._partition_updates.items():
3925 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3926 comment('Move partition %s from default to %s' %
3927 (p, u.tgt_group))
3928 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003929
3930
jiajia tangf3f842b2021-03-17 21:49:44 +08003931def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003932 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003933 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003934
3935 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003936 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003937
3938 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003939 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003940 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003941 tmp_dir = MakeTempDir('boot_', suffix='.img')
3942 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003943 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3944 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003945 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3946 if not os.path.isfile(ramdisk):
3947 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3948 return None
3949 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003950 if ramdisk_format == RamdiskFormat.LZ4:
3951 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3952 elif ramdisk_format == RamdiskFormat.GZ:
3953 with open(ramdisk, 'rb') as input_stream:
3954 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003955 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3956 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003957 p2.wait()
3958 else:
3959 logger.error('Only support lz4 or minigzip ramdisk format.')
3960 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003961
3962 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3963 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3964 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3965 # the host environment.
3966 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003967 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003968
Yifan Hongc65a0542021-01-07 14:21:01 -08003969 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3970 prop_file = os.path.join(extracted_ramdisk, search_path)
3971 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003972 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003973 logger.warning(
3974 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003975
Yifan Hong7dc51172021-01-12 11:27:39 -08003976 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003977
Yifan Hong85ac5012021-01-07 14:43:46 -08003978 except ExternalError as e:
3979 logger.warning('Unable to get boot image build props: %s', e)
3980 return None
3981
3982
3983def GetBootImageTimestamp(boot_img):
3984 """
3985 Get timestamp from ramdisk within the boot image
3986
3987 Args:
3988 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3989
3990 Return:
3991 An integer that corresponds to the timestamp of the boot image, or None
3992 if file has unknown format. Raise exception if an unexpected error has
3993 occurred.
3994 """
3995 prop_file = GetBootImageBuildProp(boot_img)
3996 if not prop_file:
3997 return None
3998
3999 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4000 if props is None:
4001 return None
4002
4003 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004004 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4005 if timestamp:
4006 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004007 logger.warning(
4008 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004009 return None
4010
4011 except ExternalError as e:
4012 logger.warning('Unable to get boot image timestamp: %s', e)
4013 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004014
4015
Kelvin Zhang26390482021-11-02 14:31:10 -07004016def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004017 if not os.path.exists(filepath):
4018 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004019 with open(filepath, 'rb') as fp:
4020 # Magic for android sparse image format
4021 # https://source.android.com/devices/bootloader/images
4022 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004023
4024def ParseUpdateEngineConfig(path: str):
4025 """Parse the update_engine config stored in file `path`
4026 Args
4027 path: Path to update_engine_config.txt file in target_files
4028
4029 Returns
4030 A tuple of (major, minor) version number . E.g. (2, 8)
4031 """
4032 with open(path, "r") as fp:
4033 # update_engine_config.txt is only supposed to contain two lines,
4034 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
4035 # sufficient. If the length is more than that, something is wrong.
4036 data = fp.read(1024)
4037 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
4038 if not major:
4039 raise ValueError(
4040 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
4041 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
4042 if not minor:
4043 raise ValueError(
4044 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
4045 return (int(major.group(1)), int(minor.group(1)))