blob: 2ae39649b26bed80a3bf7ac61c210d7d784b177c [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
Kelvin Zhangad427382021-08-12 16:19:09 -0700464 def is_vabc_xor(self):
465 vendor_prop = self.info_dict.get("vendor.build.prop")
466 vabc_xor_enabled = vendor_prop and \
467 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
468 return vabc_xor_enabled
469
470 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400471 def vendor_suppressed_vabc(self):
472 vendor_prop = self.info_dict.get("vendor.build.prop")
473 vabc_suppressed = vendor_prop and \
474 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
475 return vabc_suppressed and vabc_suppressed.lower() == "true"
476
477 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700478 def oem_props(self):
479 return self._oem_props
480
481 def __getitem__(self, key):
482 return self.info_dict[key]
483
484 def __setitem__(self, key, value):
485 self.info_dict[key] = value
486
487 def get(self, key, default=None):
488 return self.info_dict.get(key, default)
489
490 def items(self):
491 return self.info_dict.items()
492
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000493 def _GetRawBuildProp(self, prop, partition):
494 prop_file = '{}.build.prop'.format(
495 partition) if partition else 'build.prop'
496 partition_props = self.info_dict.get(prop_file)
497 if not partition_props:
498 return None
499 return partition_props.GetProp(prop)
500
Daniel Normand5fe8622020-01-08 17:01:11 -0800501 def GetPartitionBuildProp(self, prop, partition):
502 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800503
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000504 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000505 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000506 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800507
Daniel Normand5fe8622020-01-08 17:01:11 -0800508 # If provided a partition for this property, only look within that
509 # partition's build.prop.
510 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800511 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800512 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800513 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514
515 prop_val = self._GetRawBuildProp(prop, partition)
516 if prop_val is not None:
517 return prop_val
518 raise ExternalError("couldn't find %s in %s.build.prop" %
519 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800520
Tao Bao1c320f82019-10-04 23:25:12 -0700521 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800522 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700523 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
524 return self._ResolveRoProductBuildProp(prop)
525
Tianjiefdda51d2021-05-05 14:46:35 -0700526 if prop == "ro.build.id":
527 return self._GetBuildId()
528
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000529 prop_val = self._GetRawBuildProp(prop, None)
530 if prop_val is not None:
531 return prop_val
532
533 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700534
535 def _ResolveRoProductBuildProp(self, prop):
536 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000537 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700538 if prop_val:
539 return prop_val
540
Steven Laver8e2086e2020-04-27 16:26:31 -0700541 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000542 source_order_val = self._GetRawBuildProp(
543 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700544 if source_order_val:
545 source_order = source_order_val.split(",")
546 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700547 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700548
549 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700550 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700551 raise ExternalError(
552 "Invalid ro.product.property_source_order '{}'".format(source_order))
553
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000554 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700555 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000556 "ro.product", "ro.product.{}".format(source_partition), 1)
557 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700558 if prop_val:
559 return prop_val
560
561 raise ExternalError("couldn't resolve {}".format(prop))
562
Steven Laver8e2086e2020-04-27 16:26:31 -0700563 def _GetRoProductPropsDefaultSourceOrder(self):
564 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
565 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000566 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700567 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000568 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700569 if android_version == "10":
570 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
571 # NOTE: float() conversion of android_version will have rounding error.
572 # We are checking for "9" or less, and using "< 10" is well outside of
573 # possible floating point rounding.
574 try:
575 android_version_val = float(android_version)
576 except ValueError:
577 android_version_val = 0
578 if android_version_val < 10:
579 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
580 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
581
Tianjieb37c5be2020-10-15 21:27:10 -0700582 def _GetPlatformVersion(self):
583 version_sdk = self.GetBuildProp("ro.build.version.sdk")
584 # init code switches to version_release_or_codename (see b/158483506). After
585 # API finalization, release_or_codename will be the same as release. This
586 # is the best effort to support pre-S dev stage builds.
587 if int(version_sdk) >= 30:
588 try:
589 return self.GetBuildProp("ro.build.version.release_or_codename")
590 except ExternalError:
591 logger.warning('Failed to find ro.build.version.release_or_codename')
592
593 return self.GetBuildProp("ro.build.version.release")
594
Tianjiefdda51d2021-05-05 14:46:35 -0700595 def _GetBuildId(self):
596 build_id = self._GetRawBuildProp("ro.build.id", None)
597 if build_id:
598 return build_id
599
600 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
601 if not legacy_build_id:
602 raise ExternalError("Couldn't find build id in property file")
603
604 if self.use_legacy_id:
605 return legacy_build_id
606
607 # Append the top 8 chars of vbmeta digest to the existing build id. The
608 # logic needs to match the one in init, so that OTA can deliver correctly.
609 avb_enable = self.info_dict.get("avb_enable") == "true"
610 if not avb_enable:
611 raise ExternalError("AVB isn't enabled when using legacy build id")
612
613 vbmeta_digest = self.info_dict.get("vbmeta_digest")
614 if not vbmeta_digest:
615 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
616 " id")
617 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
618 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
619
620 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
621 return legacy_build_id + '.' + digest_prefix
622
Tianjieb37c5be2020-10-15 21:27:10 -0700623 def _GetPartitionPlatformVersion(self, partition):
624 try:
625 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
626 partition)
627 except ExternalError:
628 return self.GetPartitionBuildProp("ro.build.version.release",
629 partition)
630
Tao Bao1c320f82019-10-04 23:25:12 -0700631 def GetOemProperty(self, key):
632 if self.oem_props is not None and key in self.oem_props:
633 return self.oem_dicts[0][key]
634 return self.GetBuildProp(key)
635
Daniel Normand5fe8622020-01-08 17:01:11 -0800636 def GetPartitionFingerprint(self, partition):
637 return self._partition_fingerprints.get(partition, None)
638
639 def CalculatePartitionFingerprint(self, partition):
640 try:
641 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
642 except ExternalError:
643 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
644 self.GetPartitionBuildProp("ro.product.brand", partition),
645 self.GetPartitionBuildProp("ro.product.name", partition),
646 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700647 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800648 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400649 self.GetPartitionBuildProp(
650 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800651 self.GetPartitionBuildProp("ro.build.type", partition),
652 self.GetPartitionBuildProp("ro.build.tags", partition))
653
Tao Bao1c320f82019-10-04 23:25:12 -0700654 def CalculateFingerprint(self):
655 if self.oem_props is None:
656 try:
657 return self.GetBuildProp("ro.build.fingerprint")
658 except ExternalError:
659 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
660 self.GetBuildProp("ro.product.brand"),
661 self.GetBuildProp("ro.product.name"),
662 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700663 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700664 self.GetBuildProp("ro.build.id"),
665 self.GetBuildProp("ro.build.version.incremental"),
666 self.GetBuildProp("ro.build.type"),
667 self.GetBuildProp("ro.build.tags"))
668 return "%s/%s/%s:%s" % (
669 self.GetOemProperty("ro.product.brand"),
670 self.GetOemProperty("ro.product.name"),
671 self.GetOemProperty("ro.product.device"),
672 self.GetBuildProp("ro.build.thumbprint"))
673
674 def WriteMountOemScript(self, script):
675 assert self.oem_props is not None
676 recovery_mount_options = self.info_dict.get("recovery_mount_options")
677 script.Mount("/oem", recovery_mount_options)
678
679 def WriteDeviceAssertions(self, script, oem_no_mount):
680 # Read the property directly if not using OEM properties.
681 if not self.oem_props:
682 script.AssertDevice(self.device)
683 return
684
685 # Otherwise assert OEM properties.
686 if not self.oem_dicts:
687 raise ExternalError(
688 "No OEM file provided to answer expected assertions")
689
690 for prop in self.oem_props.split():
691 values = []
692 for oem_dict in self.oem_dicts:
693 if prop in oem_dict:
694 values.append(oem_dict[prop])
695 if not values:
696 raise ExternalError(
697 "The OEM file is missing the property %s" % (prop,))
698 script.AssertOemProperty(prop, values, oem_no_mount)
699
700
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000701def ReadFromInputFile(input_file, fn):
702 """Reads the contents of fn from input zipfile or directory."""
703 if isinstance(input_file, zipfile.ZipFile):
704 return input_file.read(fn).decode()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700705 elif zipfile.is_zipfile(input_file):
706 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
707 return zfp.read(fn).decode()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000708 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700709 if not os.path.isdir(input_file):
710 raise ValueError(
711 "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 +0000712 path = os.path.join(input_file, *fn.split("/"))
713 try:
714 with open(path) as f:
715 return f.read()
716 except IOError as e:
717 if e.errno == errno.ENOENT:
718 raise KeyError(fn)
719
720
Yifan Hong10482a22021-01-07 14:38:41 -0800721def ExtractFromInputFile(input_file, fn):
722 """Extracts the contents of fn from input zipfile or directory into a file."""
723 if isinstance(input_file, zipfile.ZipFile):
724 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500725 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800726 f.write(input_file.read(fn))
727 return tmp_file
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700728 elif zipfile.is_zipfile(input_file):
729 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
730 tmp_file = MakeTempFile(os.path.basename(fn))
731 with open(tmp_file, "wb") as fp:
732 fp.write(zfp.read(fn))
733 return tmp_file
Yifan Hong10482a22021-01-07 14:38:41 -0800734 else:
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700735 if not os.path.isdir(input_file):
736 raise ValueError(
737 "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 -0800738 file = os.path.join(input_file, *fn.split("/"))
739 if not os.path.exists(file):
740 raise KeyError(fn)
741 return file
742
Kelvin Zhang563750f2021-04-28 12:46:17 -0400743
jiajia tangf3f842b2021-03-17 21:49:44 +0800744class RamdiskFormat(object):
745 LZ4 = 1
746 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800747
Kelvin Zhang563750f2021-04-28 12:46:17 -0400748
TJ Rhoades6f488e92022-05-01 22:16:22 -0700749def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800750 if info_dict.get('lz4_ramdisks') == 'true':
751 ramdisk_format = RamdiskFormat.LZ4
752 else:
753 ramdisk_format = RamdiskFormat.GZ
754 return ramdisk_format
755
Kelvin Zhang563750f2021-04-28 12:46:17 -0400756
Tao Bao410ad8b2018-08-24 12:08:38 -0700757def LoadInfoDict(input_file, repacking=False):
758 """Loads the key/value pairs from the given input target_files.
759
Tianjiea85bdf02020-07-29 11:56:19 -0700760 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700761 checks and returns the parsed key/value pairs for to the given build. It's
762 usually called early when working on input target_files files, e.g. when
763 generating OTAs, or signing builds. Note that the function may be called
764 against an old target_files file (i.e. from past dessert releases). So the
765 property parsing needs to be backward compatible.
766
767 In a `META/misc_info.txt`, a few properties are stored as links to the files
768 in the PRODUCT_OUT directory. It works fine with the build system. However,
769 they are no longer available when (re)generating images from target_files zip.
770 When `repacking` is True, redirect these properties to the actual files in the
771 unzipped directory.
772
773 Args:
774 input_file: The input target_files file, which could be an open
775 zipfile.ZipFile instance, or a str for the dir that contains the files
776 unzipped from a target_files file.
777 repacking: Whether it's trying repack an target_files file after loading the
778 info dict (default: False). If so, it will rewrite a few loaded
779 properties (e.g. selinux_fc, root_dir) to point to the actual files in
780 target_files file. When doing repacking, `input_file` must be a dir.
781
782 Returns:
783 A dict that contains the parsed key/value pairs.
784
785 Raises:
786 AssertionError: On invalid input arguments.
787 ValueError: On malformed input values.
788 """
789 if repacking:
790 assert isinstance(input_file, str), \
791 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700792
Doug Zongkerc9253822014-02-04 12:17:58 -0800793 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000794 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800795
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700796 try:
Michael Runge6e836112014-04-15 17:40:21 -0700797 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700798 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700799 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700800
Tao Bao410ad8b2018-08-24 12:08:38 -0700801 if "recovery_api_version" not in d:
802 raise ValueError("Failed to find 'recovery_api_version'")
803 if "fstab_version" not in d:
804 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800805
Tao Bao410ad8b2018-08-24 12:08:38 -0700806 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700807 # "selinux_fc" properties should point to the file_contexts files
808 # (file_contexts.bin) under META/.
809 for key in d:
810 if key.endswith("selinux_fc"):
811 fc_basename = os.path.basename(d[key])
812 fc_config = os.path.join(input_file, "META", fc_basename)
813 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700814
Daniel Norman72c626f2019-05-13 15:58:14 -0700815 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700816
Tom Cherryd14b8952018-08-09 14:26:00 -0700817 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700818 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700819 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700820 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700821
David Anderson0ec64ac2019-12-06 12:21:18 -0800822 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700823 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000824 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800825 key_name = part_name + "_base_fs_file"
826 if key_name not in d:
827 continue
828 basename = os.path.basename(d[key_name])
829 base_fs_file = os.path.join(input_file, "META", basename)
830 if os.path.exists(base_fs_file):
831 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700832 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700833 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800834 "Failed to find %s base fs file: %s", part_name, base_fs_file)
835 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700836
Doug Zongker37974732010-09-16 17:44:38 -0700837 def makeint(key):
838 if key in d:
839 d[key] = int(d[key], 0)
840
841 makeint("recovery_api_version")
842 makeint("blocksize")
843 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700844 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700845 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700846 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700847 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800848 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700849
Steve Muckle903a1ca2020-05-07 17:32:10 -0700850 boot_images = "boot.img"
851 if "boot_images" in d:
852 boot_images = d["boot_images"]
853 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400854 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700855
Tao Bao765668f2019-10-04 22:03:00 -0700856 # Load recovery fstab if applicable.
857 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700858 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800859
Tianjie Xu861f4132018-09-12 11:49:33 -0700860 # Tries to load the build props for all partitions with care_map, including
861 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800862 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800863 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000864 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800865 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700866 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800867
Tao Bao3ed35d32019-10-07 20:48:48 -0700868 # Set up the salt (based on fingerprint) that will be used when adding AVB
869 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800870 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700871 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800872 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800873 fingerprint = build_info.GetPartitionFingerprint(partition)
874 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400875 d["avb_{}_salt".format(partition)] = sha256(
876 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700877
Yong Ma253b1062022-08-18 09:53:27 +0000878 # Set up the salt for partitions without build.prop
879 if build_info.fingerprint:
880 d["avb_salt"] = sha256(build_info.fingerprint.encode()).hexdigest()
881
Tianjiefdda51d2021-05-05 14:46:35 -0700882 # Set the vbmeta digest if exists
883 try:
884 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
885 except KeyError:
886 pass
887
Kelvin Zhang39aea442020-08-17 11:04:25 -0400888 try:
889 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
890 except KeyError:
891 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700892 return d
893
Tao Baod1de6f32017-03-01 16:38:48 -0800894
Daniel Norman4cc9df62019-07-18 10:11:07 -0700895def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900896 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700897 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900898
Daniel Norman4cc9df62019-07-18 10:11:07 -0700899
900def LoadDictionaryFromFile(file_path):
901 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900902 return LoadDictionaryFromLines(lines)
903
904
Michael Runge6e836112014-04-15 17:40:21 -0700905def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700906 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700907 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700908 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700909 if not line or line.startswith("#"):
910 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700911 if "=" in line:
912 name, value = line.split("=", 1)
913 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700914 return d
915
Tao Baod1de6f32017-03-01 16:38:48 -0800916
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000917class PartitionBuildProps(object):
918 """The class holds the build prop of a particular partition.
919
920 This class loads the build.prop and holds the build properties for a given
921 partition. It also partially recognizes the 'import' statement in the
922 build.prop; and calculates alternative values of some specific build
923 properties during runtime.
924
925 Attributes:
926 input_file: a zipped target-file or an unzipped target-file directory.
927 partition: name of the partition.
928 props_allow_override: a list of build properties to search for the
929 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000930 build_props: a dict of build properties for the given partition.
931 prop_overrides: a set of props that are overridden by import.
932 placeholder_values: A dict of runtime variables' values to replace the
933 placeholders in the build.prop file. We expect exactly one value for
934 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800935 ramdisk_format: If name is "boot", the format of ramdisk inside the
936 boot image. Otherwise, its value is ignored.
937 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000938 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400939
Tianjie Xu9afb2212020-05-10 21:48:15 +0000940 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000941 self.input_file = input_file
942 self.partition = name
943 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000944 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000945 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000946 self.prop_overrides = set()
947 self.placeholder_values = {}
948 if placeholder_values:
949 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000950
951 @staticmethod
952 def FromDictionary(name, build_props):
953 """Constructs an instance from a build prop dictionary."""
954
955 props = PartitionBuildProps("unknown", name)
956 props.build_props = build_props.copy()
957 return props
958
959 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800960 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000961 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800962
Devin Mooreafdd7c72021-12-13 22:04:08 +0000963 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400964 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000965 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800966 else:
967 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
968
969 props = PartitionBuildProps(input_file, name, placeholder_values)
970 props._LoadBuildProp(data)
971 return props
972
973 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000974 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800975 """
976 Read build.prop for boot image from input_file.
977 Return empty string if not found.
978 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000979 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800980 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000981 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800982 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000983 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800984 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800985 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800986 if prop_file is None:
987 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500988 with open(prop_file, "r") as f:
989 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800990
991 @staticmethod
992 def _ReadPartitionPropFile(input_file, name):
993 """
994 Read build.prop for name from input_file.
995 Return empty string if not found.
996 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000997 data = ''
998 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
999 '{}/build.prop'.format(name.upper())]:
1000 try:
1001 data = ReadFromInputFile(input_file, prop_file)
1002 break
1003 except KeyError:
1004 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -08001005 if data == '':
1006 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -08001007 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001008
Yifan Hong125d0b62020-09-24 17:07:03 -07001009 @staticmethod
1010 def FromBuildPropFile(name, build_prop_file):
1011 """Constructs an instance from a build prop file."""
1012
1013 props = PartitionBuildProps("unknown", name)
1014 with open(build_prop_file) as f:
1015 props._LoadBuildProp(f.read())
1016 return props
1017
Tianjie Xu9afb2212020-05-10 21:48:15 +00001018 def _LoadBuildProp(self, data):
1019 for line in data.split('\n'):
1020 line = line.strip()
1021 if not line or line.startswith("#"):
1022 continue
1023 if line.startswith("import"):
1024 overrides = self._ImportParser(line)
1025 duplicates = self.prop_overrides.intersection(overrides.keys())
1026 if duplicates:
1027 raise ValueError('prop {} is overridden multiple times'.format(
1028 ','.join(duplicates)))
1029 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1030 self.build_props.update(overrides)
1031 elif "=" in line:
1032 name, value = line.split("=", 1)
1033 if name in self.prop_overrides:
1034 raise ValueError('prop {} is set again after overridden by import '
1035 'statement'.format(name))
1036 self.build_props[name] = value
1037
1038 def _ImportParser(self, line):
1039 """Parses the build prop in a given import statement."""
1040
1041 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001042 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001043 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001044
1045 if len(tokens) == 3:
1046 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1047 return {}
1048
Tianjie Xu9afb2212020-05-10 21:48:15 +00001049 import_path = tokens[1]
1050 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001051 logger.warn('Unrecognized import path {}'.format(line))
1052 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001053
1054 # We only recognize a subset of import statement that the init process
1055 # supports. And we can loose the restriction based on how the dynamic
1056 # fingerprint is used in practice. The placeholder format should be
1057 # ${placeholder}, and its value should be provided by the caller through
1058 # the placeholder_values.
1059 for prop, value in self.placeholder_values.items():
1060 prop_place_holder = '${{{}}}'.format(prop)
1061 if prop_place_holder in import_path:
1062 import_path = import_path.replace(prop_place_holder, value)
1063 if '$' in import_path:
1064 logger.info('Unresolved place holder in import path %s', import_path)
1065 return {}
1066
1067 import_path = import_path.replace('/{}'.format(self.partition),
1068 self.partition.upper())
1069 logger.info('Parsing build props override from %s', import_path)
1070
1071 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1072 d = LoadDictionaryFromLines(lines)
1073 return {key: val for key, val in d.items()
1074 if key in self.props_allow_override}
1075
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001076 def __getstate__(self):
1077 state = self.__dict__.copy()
1078 # Don't pickle baz
1079 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1080 state["input_file"] = state["input_file"].filename
1081 return state
1082
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001083 def GetProp(self, prop):
1084 return self.build_props.get(prop)
1085
1086
Tianjie Xucfa86222016-03-07 16:31:19 -08001087def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1088 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001089 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001090 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001091 self.mount_point = mount_point
1092 self.fs_type = fs_type
1093 self.device = device
1094 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001095 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001096 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001097
1098 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001099 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001100 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001101 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001102 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001103
Tao Baod1de6f32017-03-01 16:38:48 -08001104 assert fstab_version == 2
1105
1106 d = {}
1107 for line in data.split("\n"):
1108 line = line.strip()
1109 if not line or line.startswith("#"):
1110 continue
1111
1112 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1113 pieces = line.split()
1114 if len(pieces) != 5:
1115 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1116
1117 # Ignore entries that are managed by vold.
1118 options = pieces[4]
1119 if "voldmanaged=" in options:
1120 continue
1121
1122 # It's a good line, parse it.
1123 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001124 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001125 options = options.split(",")
1126 for i in options:
1127 if i.startswith("length="):
1128 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001129 elif i == "slotselect":
1130 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001131 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001132 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001134
Tao Baod1de6f32017-03-01 16:38:48 -08001135 mount_flags = pieces[3]
1136 # Honor the SELinux context if present.
1137 context = None
1138 for i in mount_flags.split(","):
1139 if i.startswith("context="):
1140 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001141
Tao Baod1de6f32017-03-01 16:38:48 -08001142 mount_point = pieces[1]
1143 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001144 device=pieces[0], length=length, context=context,
1145 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001146
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001147 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001148 # system. Other areas assume system is always at "/system" so point /system
1149 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001150 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001151 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001152 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001153 return d
1154
1155
Tao Bao765668f2019-10-04 22:03:00 -07001156def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1157 """Finds the path to recovery fstab and loads its contents."""
1158 # recovery fstab is only meaningful when installing an update via recovery
1159 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001160 if info_dict.get('ab_update') == 'true' and \
1161 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001162 return None
1163
1164 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1165 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1166 # cases, since it may load the info_dict from an old build (e.g. when
1167 # generating incremental OTAs from that build).
1168 system_root_image = info_dict.get('system_root_image') == 'true'
1169 if info_dict.get('no_recovery') != 'true':
1170 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1171 if isinstance(input_file, zipfile.ZipFile):
1172 if recovery_fstab_path not in input_file.namelist():
1173 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1174 else:
1175 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1176 if not os.path.exists(path):
1177 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1178 return LoadRecoveryFSTab(
1179 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1180 system_root_image)
1181
1182 if info_dict.get('recovery_as_boot') == 'true':
1183 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1184 if isinstance(input_file, zipfile.ZipFile):
1185 if recovery_fstab_path not in input_file.namelist():
1186 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1187 else:
1188 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1189 if not os.path.exists(path):
1190 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1191 return LoadRecoveryFSTab(
1192 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1193 system_root_image)
1194
1195 return None
1196
1197
Doug Zongker37974732010-09-16 17:44:38 -07001198def DumpInfoDict(d):
1199 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001200 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001201
Dan Albert8b72aef2015-03-23 19:13:21 -07001202
Daniel Norman55417142019-11-25 16:04:36 -08001203def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001204 """Merges dynamic partition info variables.
1205
1206 Args:
1207 framework_dict: The dictionary of dynamic partition info variables from the
1208 partial framework target files.
1209 vendor_dict: The dictionary of dynamic partition info variables from the
1210 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001211
1212 Returns:
1213 The merged dynamic partition info dictionary.
1214 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001215
1216 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001217 combined = set(a.split())
1218 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001219 combined = [item.strip() for item in combined if item.strip()]
1220 return " ".join(sorted(combined))
1221
1222 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001223 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001224 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1225
1226 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001227 # For keys-value pairs that are the same, copy to merged dict
1228 for key in vendor_dict.keys():
1229 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1230 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001231
1232 merged_dict["dynamic_partition_list"] = uniq_concat(
1233 framework_dict.get("dynamic_partition_list", ""),
1234 vendor_dict.get("dynamic_partition_list", ""))
1235
1236 # Super block devices are defined by the vendor dict.
1237 if "super_block_devices" in vendor_dict:
1238 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001239 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001240 key = "super_%s_device_size" % block_device
1241 if key not in vendor_dict:
1242 raise ValueError("Vendor dict does not contain required key %s." % key)
1243 merged_dict[key] = vendor_dict[key]
1244
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001245 # Partition groups and group sizes are defined by the vendor dict because
1246 # these values may vary for each board that uses a shared system image.
1247 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001248 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001249 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001250 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001251 if key not in vendor_dict:
1252 raise ValueError("Vendor dict does not contain required key %s." % key)
1253 merged_dict[key] = vendor_dict[key]
1254
1255 # Set the partition group's partition list using a concatenation of the
1256 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001257 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001258 merged_dict[key] = uniq_concat(
1259 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301260
Daniel Normanb0c75912020-09-24 14:30:21 -07001261 # Various other flags should be copied from the vendor dict, if defined.
1262 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1263 "super_metadata_device", "super_partition_error_limit",
1264 "super_partition_size"):
1265 if key in vendor_dict.keys():
1266 merged_dict[key] = vendor_dict[key]
1267
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001268 return merged_dict
1269
1270
Daniel Norman21c34f72020-11-11 17:25:50 -08001271def PartitionMapFromTargetFiles(target_files_dir):
1272 """Builds a map from partition -> path within an extracted target files directory."""
1273 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1274 possible_subdirs = {
1275 "system": ["SYSTEM"],
1276 "vendor": ["VENDOR", "SYSTEM/vendor"],
1277 "product": ["PRODUCT", "SYSTEM/product"],
1278 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1279 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1280 "vendor_dlkm": [
1281 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1282 ],
1283 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001284 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001285 }
1286 partition_map = {}
1287 for partition, subdirs in possible_subdirs.items():
1288 for subdir in subdirs:
1289 if os.path.exists(os.path.join(target_files_dir, subdir)):
1290 partition_map[partition] = subdir
1291 break
1292 return partition_map
1293
1294
Daniel Normand3351562020-10-29 12:33:11 -07001295def SharedUidPartitionViolations(uid_dict, partition_groups):
1296 """Checks for APK sharedUserIds that cross partition group boundaries.
1297
1298 This uses a single or merged build's shareduid_violation_modules.json
1299 output file, as generated by find_shareduid_violation.py or
1300 core/tasks/find-shareduid-violation.mk.
1301
1302 An error is defined as a sharedUserId that is found in a set of partitions
1303 that span more than one partition group.
1304
1305 Args:
1306 uid_dict: A dictionary created by using the standard json module to read a
1307 complete shareduid_violation_modules.json file.
1308 partition_groups: A list of groups, where each group is a list of
1309 partitions.
1310
1311 Returns:
1312 A list of error messages.
1313 """
1314 errors = []
1315 for uid, partitions in uid_dict.items():
1316 found_in_groups = [
1317 group for group in partition_groups
1318 if set(partitions.keys()) & set(group)
1319 ]
1320 if len(found_in_groups) > 1:
1321 errors.append(
1322 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1323 % (uid, ",".join(sorted(partitions.keys()))))
1324 return errors
1325
1326
Daniel Norman21c34f72020-11-11 17:25:50 -08001327def RunHostInitVerifier(product_out, partition_map):
1328 """Runs host_init_verifier on the init rc files within partitions.
1329
1330 host_init_verifier searches the etc/init path within each partition.
1331
1332 Args:
1333 product_out: PRODUCT_OUT directory, containing partition directories.
1334 partition_map: A map of partition name -> relative path within product_out.
1335 """
1336 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1337 cmd = ["host_init_verifier"]
1338 for partition, path in partition_map.items():
1339 if partition not in allowed_partitions:
1340 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1341 partition)
1342 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1343 # Add --property-contexts if the file exists on the partition.
1344 property_contexts = "%s_property_contexts" % (
1345 "plat" if partition == "system" else partition)
1346 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1347 property_contexts)
1348 if os.path.exists(property_contexts_path):
1349 cmd.append("--property-contexts=%s" % property_contexts_path)
1350 # Add the passwd file if the file exists on the partition.
1351 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1352 if os.path.exists(passwd_path):
1353 cmd.extend(["-p", passwd_path])
1354 return RunAndCheckOutput(cmd)
1355
1356
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001357def AppendAVBSigningArgs(cmd, partition):
1358 """Append signing arguments for avbtool."""
1359 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1360 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001361 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1362 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1363 if os.path.exists(new_key_path):
1364 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001365 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1366 if key_path and algorithm:
1367 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001368 avb_salt = OPTIONS.info_dict.get("avb_salt")
1369 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001370 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001371 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001372
1373
Tao Bao765668f2019-10-04 22:03:00 -07001374def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001375 """Returns the VBMeta arguments for partition.
1376
1377 It sets up the VBMeta argument by including the partition descriptor from the
1378 given 'image', or by configuring the partition as a chained partition.
1379
1380 Args:
1381 partition: The name of the partition (e.g. "system").
1382 image: The path to the partition image.
1383 info_dict: A dict returned by common.LoadInfoDict(). Will use
1384 OPTIONS.info_dict if None has been given.
1385
1386 Returns:
1387 A list of VBMeta arguments.
1388 """
1389 if info_dict is None:
1390 info_dict = OPTIONS.info_dict
1391
1392 # Check if chain partition is used.
1393 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001394 if not key_path:
1395 return ["--include_descriptors_from_image", image]
1396
1397 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1398 # into vbmeta.img. The recovery image will be configured on an independent
1399 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1400 # See details at
1401 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001402 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001403 return []
1404
1405 # Otherwise chain the partition into vbmeta.
1406 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1407 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001408
1409
Tao Bao02a08592018-07-22 12:40:45 -07001410def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1411 """Constructs and returns the arg to build or verify a chained partition.
1412
1413 Args:
1414 partition: The partition name.
1415 info_dict: The info dict to look up the key info and rollback index
1416 location.
1417 key: The key to be used for building or verifying the partition. Defaults to
1418 the key listed in info_dict.
1419
1420 Returns:
1421 A string of form "partition:rollback_index_location:key" that can be used to
1422 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001423 """
1424 if key is None:
1425 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001426 if key and not os.path.exists(key) and OPTIONS.search_path:
1427 new_key_path = os.path.join(OPTIONS.search_path, key)
1428 if os.path.exists(new_key_path):
1429 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001430 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001431 rollback_index_location = info_dict[
1432 "avb_" + partition + "_rollback_index_location"]
1433 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1434
1435
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001436def _HasGkiCertificationArgs():
1437 return ("gki_signing_key_path" in OPTIONS.info_dict and
1438 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001439
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001440
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001441def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001442 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001443 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001444
1445 if not os.path.exists(key_path) and OPTIONS.search_path:
1446 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1447 if os.path.exists(new_key_path):
1448 key_path = new_key_path
1449
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001450 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001451 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001452 raise ExternalError(
1453 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001454
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001455 output_certificate = tempfile.NamedTemporaryFile()
1456 cmd = [
1457 "generate_gki_certificate",
1458 "--name", image_name,
1459 "--algorithm", algorithm,
1460 "--key", key_path,
1461 "--output", output_certificate.name,
1462 image,
1463 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001464
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001465 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1466 signature_args = signature_args.strip()
1467 if signature_args:
1468 cmd.extend(["--additional_avb_args", signature_args])
1469
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001470 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001471 args = args.strip()
1472 if args:
1473 cmd.extend(["--additional_avb_args", args])
1474
1475 RunAndCheckOutput(cmd)
1476
1477 output_certificate.seek(os.SEEK_SET, 0)
1478 data = output_certificate.read()
1479 output_certificate.close()
1480 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001481
1482
Daniel Norman276f0622019-07-26 14:13:51 -07001483def BuildVBMeta(image_path, partitions, name, needed_partitions):
1484 """Creates a VBMeta image.
1485
1486 It generates the requested VBMeta image. The requested image could be for
1487 top-level or chained VBMeta image, which is determined based on the name.
1488
1489 Args:
1490 image_path: The output path for the new VBMeta image.
1491 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001492 values. Only valid partition names are accepted, as partitions listed
1493 in common.AVB_PARTITIONS and custom partitions listed in
1494 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001495 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1496 needed_partitions: Partitions whose descriptors should be included into the
1497 generated VBMeta image.
1498
1499 Raises:
1500 AssertionError: On invalid input args.
1501 """
1502 avbtool = OPTIONS.info_dict["avb_avbtool"]
1503 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1504 AppendAVBSigningArgs(cmd, name)
1505
Hongguang Chenf23364d2020-04-27 18:36:36 -07001506 custom_partitions = OPTIONS.info_dict.get(
1507 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001508 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 -07001509
Daniel Norman276f0622019-07-26 14:13:51 -07001510 for partition, path in partitions.items():
1511 if partition not in needed_partitions:
1512 continue
1513 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001514 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001515 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001516 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001517 'Unknown partition: {}'.format(partition)
1518 assert os.path.exists(path), \
1519 'Failed to find {} for {}'.format(path, partition)
1520 cmd.extend(GetAvbPartitionArg(partition, path))
1521
1522 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1523 if args and args.strip():
1524 split_args = shlex.split(args)
1525 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001526 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001527 # as a path relative to source tree, which may not be available at the
1528 # same location when running this script (we have the input target_files
1529 # zip only). For such cases, we additionally scan other locations (e.g.
1530 # IMAGES/, RADIO/, etc) before bailing out.
1531 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001532 chained_image = split_args[index + 1]
1533 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001534 continue
1535 found = False
1536 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1537 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001538 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001539 if os.path.exists(alt_path):
1540 split_args[index + 1] = alt_path
1541 found = True
1542 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001543 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001544 cmd.extend(split_args)
1545
1546 RunAndCheckOutput(cmd)
1547
1548
jiajia tang836f76b2021-04-02 14:48:26 +08001549def _MakeRamdisk(sourcedir, fs_config_file=None,
1550 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001551 ramdisk_img = tempfile.NamedTemporaryFile()
1552
1553 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1554 cmd = ["mkbootfs", "-f", fs_config_file,
1555 os.path.join(sourcedir, "RAMDISK")]
1556 else:
1557 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1558 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001559 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001560 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001561 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001562 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001563 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001564 else:
1565 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001566
1567 p2.wait()
1568 p1.wait()
1569 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001570 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001571
1572 return ramdisk_img
1573
1574
Steve Muckle9793cf62020-04-08 18:27:00 -07001575def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001576 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001577 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001578
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001579 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001580 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1581 we are building a two-step special image (i.e. building a recovery image to
1582 be loaded into /boot in two-step OTAs).
1583
1584 Return the image data, or None if sourcedir does not appear to contains files
1585 for building the requested image.
1586 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001587
Yifan Hong63c5ca12020-10-08 11:54:02 -07001588 if info_dict is None:
1589 info_dict = OPTIONS.info_dict
1590
Steve Muckle9793cf62020-04-08 18:27:00 -07001591 # "boot" or "recovery", without extension.
1592 partition_name = os.path.basename(sourcedir).lower()
1593
Yifan Hong63c5ca12020-10-08 11:54:02 -07001594 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001595 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001596 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1597 logger.info("Excluded kernel binary from recovery image.")
1598 else:
1599 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001600 elif partition_name == "init_boot":
1601 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001602 else:
1603 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001604 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001605 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001606 return None
1607
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001608 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1609
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001610 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001611 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001612
Doug Zongkereef39442009-04-02 12:14:19 -07001613 img = tempfile.NamedTemporaryFile()
1614
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001615 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001616 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001617 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1618 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001619
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001620 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1621 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1622
Yifan Hong63c5ca12020-10-08 11:54:02 -07001623 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001624 if kernel_path is not None:
1625 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001626
Benoit Fradina45a8682014-07-14 21:00:43 +02001627 fn = os.path.join(sourcedir, "second")
1628 if os.access(fn, os.F_OK):
1629 cmd.append("--second")
1630 cmd.append(fn)
1631
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001632 fn = os.path.join(sourcedir, "dtb")
1633 if os.access(fn, os.F_OK):
1634 cmd.append("--dtb")
1635 cmd.append(fn)
1636
Doug Zongker171f1cd2009-06-15 22:36:37 -07001637 fn = os.path.join(sourcedir, "cmdline")
1638 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001639 cmd.append("--cmdline")
1640 cmd.append(open(fn).read().rstrip("\n"))
1641
1642 fn = os.path.join(sourcedir, "base")
1643 if os.access(fn, os.F_OK):
1644 cmd.append("--base")
1645 cmd.append(open(fn).read().rstrip("\n"))
1646
Ying Wang4de6b5b2010-08-25 14:29:34 -07001647 fn = os.path.join(sourcedir, "pagesize")
1648 if os.access(fn, os.F_OK):
1649 cmd.append("--pagesize")
1650 cmd.append(open(fn).read().rstrip("\n"))
1651
Steve Mucklef84668e2020-03-16 19:13:46 -07001652 if partition_name == "recovery":
1653 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301654 if not args:
1655 # Fall back to "mkbootimg_args" for recovery image
1656 # in case "recovery_mkbootimg_args" is not set.
1657 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001658 elif partition_name == "init_boot":
1659 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001660 else:
1661 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001662 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001663 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001664
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001665 args = info_dict.get("mkbootimg_version_args")
1666 if args and args.strip():
1667 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001668
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001669 if has_ramdisk:
1670 cmd.extend(["--ramdisk", ramdisk_img.name])
1671
Tao Baod95e9fd2015-03-29 23:07:41 -07001672 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001673 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001674 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001675 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001676 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001677 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001678
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001679 if partition_name == "recovery":
1680 if info_dict.get("include_recovery_dtbo") == "true":
1681 fn = os.path.join(sourcedir, "recovery_dtbo")
1682 cmd.extend(["--recovery_dtbo", fn])
1683 if info_dict.get("include_recovery_acpio") == "true":
1684 fn = os.path.join(sourcedir, "recovery_acpio")
1685 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001686
Tao Bao986ee862018-10-04 15:46:16 -07001687 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001688
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001689 if _HasGkiCertificationArgs():
1690 if not os.path.exists(img.name):
1691 raise ValueError("Cannot find GKI boot.img")
1692 if kernel_path is None or not os.path.exists(kernel_path):
1693 raise ValueError("Cannot find GKI kernel.img")
1694
1695 # Certify GKI images.
1696 boot_signature_bytes = b''
1697 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1698 boot_signature_bytes += _GenerateGkiCertificate(
1699 kernel_path, "generic_kernel")
1700
1701 BOOT_SIGNATURE_SIZE = 16 * 1024
1702 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1703 raise ValueError(
1704 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1705 boot_signature_bytes += (
1706 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1707 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1708
1709 with open(img.name, 'ab') as f:
1710 f.write(boot_signature_bytes)
1711
Tao Baod95e9fd2015-03-29 23:07:41 -07001712 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001713 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001714 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001715 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001716 # We have switched from the prebuilt futility binary to using the tool
1717 # (futility-host) built from the source. Override the setting in the old
1718 # TF.zip.
1719 futility = info_dict["futility"]
1720 if futility.startswith("prebuilts/"):
1721 futility = "futility-host"
1722 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001723 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001724 info_dict["vboot_key"] + ".vbprivk",
1725 info_dict["vboot_subkey"] + ".vbprivk",
1726 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001727 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001728 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001729
Tao Baof3282b42015-04-01 11:21:55 -07001730 # Clean up the temp files.
1731 img_unsigned.close()
1732 img_keyblock.close()
1733
David Zeuthen8fecb282017-12-01 16:24:01 -05001734 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001735 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001736 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001737 if partition_name == "recovery":
1738 part_size = info_dict["recovery_size"]
1739 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001740 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001741 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001742 "--partition_size", str(part_size), "--partition_name",
1743 partition_name]
1744 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001745 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001746 if args and args.strip():
1747 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001748 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001749
1750 img.seek(os.SEEK_SET, 0)
1751 data = img.read()
1752
1753 if has_ramdisk:
1754 ramdisk_img.close()
1755 img.close()
1756
1757 return data
1758
1759
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001760def _SignBootableImage(image_path, prebuilt_name, partition_name,
1761 info_dict=None):
1762 """Performs AVB signing for a prebuilt boot.img.
1763
1764 Args:
1765 image_path: The full path of the image, e.g., /path/to/boot.img.
1766 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001767 boot-5.10.img, recovery.img or init_boot.img.
1768 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001769 info_dict: The information dict read from misc_info.txt.
1770 """
1771 if info_dict is None:
1772 info_dict = OPTIONS.info_dict
1773
1774 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1775 if info_dict.get("avb_enable") == "true":
1776 avbtool = info_dict["avb_avbtool"]
1777 if partition_name == "recovery":
1778 part_size = info_dict["recovery_size"]
1779 else:
1780 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1781
1782 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1783 "--partition_size", str(part_size), "--partition_name",
1784 partition_name]
1785 AppendAVBSigningArgs(cmd, partition_name)
1786 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1787 if args and args.strip():
1788 cmd.extend(shlex.split(args))
1789 RunAndCheckOutput(cmd)
1790
1791
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001792def HasRamdisk(partition_name, info_dict=None):
1793 """Returns true/false to see if a bootable image should have a ramdisk.
1794
1795 Args:
1796 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1797 info_dict: The information dict read from misc_info.txt.
1798 """
1799 if info_dict is None:
1800 info_dict = OPTIONS.info_dict
1801
1802 if partition_name != "boot":
1803 return True # init_boot.img or recovery.img has a ramdisk.
1804
1805 if info_dict.get("recovery_as_boot") == "true":
1806 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1807
Bowgo Tsai85578e02022-04-19 10:50:59 +08001808 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1809 return False # A GKI boot.img has no ramdisk since Android-13.
1810
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001811 if info_dict.get("system_root_image") == "true":
1812 # The ramdisk content is merged into the system.img, so there is NO
1813 # ramdisk in the boot.img or boot-<kernel version>.img.
1814 return False
1815
1816 if info_dict.get("init_boot") == "true":
1817 # The ramdisk is moved to the init_boot.img, so there is NO
1818 # ramdisk in the boot.img or boot-<kernel version>.img.
1819 return False
1820
1821 return True
1822
1823
Doug Zongkerd5131602012-08-02 14:46:42 -07001824def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001825 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001826 """Return a File object with the desired bootable image.
1827
1828 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1829 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1830 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001831
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001832 if info_dict is None:
1833 info_dict = OPTIONS.info_dict
1834
Doug Zongker55d93282011-01-25 17:03:34 -08001835 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1836 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001837 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001838 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001839
1840 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1841 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001842 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001843 return File.FromLocalFile(name, prebuilt_path)
1844
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001845 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001846 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1847 if os.path.exists(prebuilt_path):
1848 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1849 signed_img = MakeTempFile()
1850 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001851 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1852 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001853
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001854 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001855
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001856 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001857
Doug Zongker6f1d0312014-08-22 08:07:12 -07001858 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001859 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001860 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001861 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001862 if data:
1863 return File(name, data)
1864 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001865
Doug Zongkereef39442009-04-02 12:14:19 -07001866
Lucas Wei03230252022-04-18 16:00:40 +08001867def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001868 """Build a vendor boot image from the specified sourcedir.
1869
1870 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1871 turn them into a vendor boot image.
1872
1873 Return the image data, or None if sourcedir does not appear to contains files
1874 for building the requested image.
1875 """
1876
1877 if info_dict is None:
1878 info_dict = OPTIONS.info_dict
1879
1880 img = tempfile.NamedTemporaryFile()
1881
TJ Rhoades6f488e92022-05-01 22:16:22 -07001882 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001883 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001884
1885 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1886 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1887
1888 cmd = [mkbootimg]
1889
1890 fn = os.path.join(sourcedir, "dtb")
1891 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001892 has_vendor_kernel_boot = (info_dict.get(
1893 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001894
1895 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1896 # Otherwise pack dtb into vendor_boot.
1897 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1898 cmd.append("--dtb")
1899 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001900
1901 fn = os.path.join(sourcedir, "vendor_cmdline")
1902 if os.access(fn, os.F_OK):
1903 cmd.append("--vendor_cmdline")
1904 cmd.append(open(fn).read().rstrip("\n"))
1905
1906 fn = os.path.join(sourcedir, "base")
1907 if os.access(fn, os.F_OK):
1908 cmd.append("--base")
1909 cmd.append(open(fn).read().rstrip("\n"))
1910
1911 fn = os.path.join(sourcedir, "pagesize")
1912 if os.access(fn, os.F_OK):
1913 cmd.append("--pagesize")
1914 cmd.append(open(fn).read().rstrip("\n"))
1915
1916 args = info_dict.get("mkbootimg_args")
1917 if args and args.strip():
1918 cmd.extend(shlex.split(args))
1919
1920 args = info_dict.get("mkbootimg_version_args")
1921 if args and args.strip():
1922 cmd.extend(shlex.split(args))
1923
1924 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1925 cmd.extend(["--vendor_boot", img.name])
1926
Devin Moore50509012021-01-13 10:45:04 -08001927 fn = os.path.join(sourcedir, "vendor_bootconfig")
1928 if os.access(fn, os.F_OK):
1929 cmd.append("--vendor_bootconfig")
1930 cmd.append(fn)
1931
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001932 ramdisk_fragment_imgs = []
1933 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1934 if os.access(fn, os.F_OK):
1935 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1936 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001937 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1938 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001939 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001940 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1941 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001942 # Use prebuilt image if found, else create ramdisk from supplied files.
1943 if os.access(fn, os.F_OK):
1944 ramdisk_fragment_pathname = fn
1945 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001946 ramdisk_fragment_root = os.path.join(
1947 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001948 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1949 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001950 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1951 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1952 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1953
Steve Mucklee1b10862019-07-10 10:49:37 -07001954 RunAndCheckOutput(cmd)
1955
1956 # AVB: if enabled, calculate and add hash.
1957 if info_dict.get("avb_enable") == "true":
1958 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001959 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001960 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001961 "--partition_size", str(part_size), "--partition_name", partition_name]
1962 AppendAVBSigningArgs(cmd, partition_name)
1963 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001964 if args and args.strip():
1965 cmd.extend(shlex.split(args))
1966 RunAndCheckOutput(cmd)
1967
1968 img.seek(os.SEEK_SET, 0)
1969 data = img.read()
1970
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001971 for f in ramdisk_fragment_imgs:
1972 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001973 ramdisk_img.close()
1974 img.close()
1975
1976 return data
1977
1978
1979def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1980 info_dict=None):
1981 """Return a File object with the desired vendor boot image.
1982
1983 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1984 the source files in 'unpack_dir'/'tree_subdir'."""
1985
1986 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1987 if os.path.exists(prebuilt_path):
1988 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1989 return File.FromLocalFile(name, prebuilt_path)
1990
1991 logger.info("building image from target_files %s...", tree_subdir)
1992
1993 if info_dict is None:
1994 info_dict = OPTIONS.info_dict
1995
Kelvin Zhang0876c412020-06-23 15:06:58 -04001996 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001997 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1998 if data:
1999 return File(name, data)
2000 return None
2001
2002
2003def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002004 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002005 """Return a File object with the desired vendor kernel boot image.
2006
2007 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2008 the source files in 'unpack_dir'/'tree_subdir'."""
2009
2010 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2011 if os.path.exists(prebuilt_path):
2012 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2013 return File.FromLocalFile(name, prebuilt_path)
2014
2015 logger.info("building image from target_files %s...", tree_subdir)
2016
2017 if info_dict is None:
2018 info_dict = OPTIONS.info_dict
2019
2020 data = _BuildVendorBootImage(
2021 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002022 if data:
2023 return File(name, data)
2024 return None
2025
2026
Narayan Kamatha07bf042017-08-14 14:49:21 +01002027def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002028 """Gunzips the given gzip compressed file to a given output file."""
2029 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002030 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002031 shutil.copyfileobj(in_file, out_file)
2032
2033
Tao Bao0ff15de2019-03-20 11:26:06 -07002034def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002035 """Unzips the archive to the given directory.
2036
2037 Args:
2038 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002039 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002040 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2041 archvie. Non-matching patterns will be filtered out. If there's no match
2042 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002043 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002044 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002045 if patterns is not None:
2046 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002047 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002048 names = input_zip.namelist()
2049 filtered = [
2050 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2051
2052 # There isn't any matching files. Don't unzip anything.
2053 if not filtered:
2054 return
2055 cmd.extend(filtered)
2056
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002057 RunAndCheckOutput(cmd)
2058
2059
Daniel Norman78554ea2021-09-14 10:29:38 -07002060def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002061 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002062
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002063 Args:
2064 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2065 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2066
Daniel Norman78554ea2021-09-14 10:29:38 -07002067 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002068 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002069
Tao Bao1c830bf2017-12-25 10:43:47 -08002070 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002071 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002072 """
Doug Zongkereef39442009-04-02 12:14:19 -07002073
Tao Bao1c830bf2017-12-25 10:43:47 -08002074 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002075 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2076 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002077 UnzipToDir(m.group(1), tmp, patterns)
2078 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002079 filename = m.group(1)
2080 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002081 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002082
Tao Baodba59ee2018-01-09 13:21:02 -08002083 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002084
2085
Yifan Hong8a66a712019-04-04 15:37:57 -07002086def GetUserImage(which, tmpdir, input_zip,
2087 info_dict=None,
2088 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002089 reset_file_map=False):
2090 """Returns an Image object suitable for passing to BlockImageDiff.
2091
2092 This function loads the specified image from the given path. If the specified
2093 image is sparse, it also performs additional processing for OTA purpose. For
2094 example, it always adds block 0 to clobbered blocks list. It also detects
2095 files that cannot be reconstructed from the block list, for whom we should
2096 avoid applying imgdiff.
2097
2098 Args:
2099 which: The partition name.
2100 tmpdir: The directory that contains the prebuilt image and block map file.
2101 input_zip: The target-files ZIP archive.
2102 info_dict: The dict to be looked up for relevant info.
2103 allow_shared_blocks: If image is sparse, whether having shared blocks is
2104 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002105 reset_file_map: If true and image is sparse, reset file map before returning
2106 the image.
2107 Returns:
2108 A Image object. If it is a sparse image and reset_file_map is False, the
2109 image will have file_map info loaded.
2110 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002111 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002112 info_dict = LoadInfoDict(input_zip)
2113
2114 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002115 if info_dict.get(which + "_disable_sparse"):
2116 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002117
2118 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2119 # shared blocks (i.e. some blocks will show up in multiple files' block
2120 # list). We can only allocate such shared blocks to the first "owner", and
2121 # disable imgdiff for all later occurrences.
2122 if allow_shared_blocks is None:
2123 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2124
2125 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002126 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002127 if reset_file_map:
2128 img.ResetFileMap()
2129 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002130 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002131
2132
hungweichencc9c05d2022-08-23 05:45:42 +00002133def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002134 """Returns a Image object suitable for passing to BlockImageDiff.
2135
2136 This function loads the specified non-sparse image from the given path.
2137
2138 Args:
2139 which: The partition name.
2140 tmpdir: The directory that contains the prebuilt image and block map file.
2141 Returns:
2142 A Image object.
2143 """
2144 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2145 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2146
2147 # The image and map files must have been created prior to calling
2148 # ota_from_target_files.py (since LMP).
2149 assert os.path.exists(path) and os.path.exists(mappath)
2150
hungweichencc9c05d2022-08-23 05:45:42 +00002151 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002152
Yifan Hong8a66a712019-04-04 15:37:57 -07002153
hungweichencc9c05d2022-08-23 05:45:42 +00002154def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002155 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2156
2157 This function loads the specified sparse image from the given path, and
2158 performs additional processing for OTA purpose. For example, it always adds
2159 block 0 to clobbered blocks list. It also detects files that cannot be
2160 reconstructed from the block list, for whom we should avoid applying imgdiff.
2161
2162 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002163 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002164 tmpdir: The directory that contains the prebuilt image and block map file.
2165 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002166 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002167 Returns:
2168 A SparseImage object, with file_map info loaded.
2169 """
Tao Baoc765cca2018-01-31 17:32:40 -08002170 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2171 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2172
2173 # The image and map files must have been created prior to calling
2174 # ota_from_target_files.py (since LMP).
2175 assert os.path.exists(path) and os.path.exists(mappath)
2176
2177 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2178 # it to clobbered_blocks so that it will be written to the target
2179 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2180 clobbered_blocks = "0"
2181
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002182 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002183 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002184
2185 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2186 # if they contain all zeros. We can't reconstruct such a file from its block
2187 # list. Tag such entries accordingly. (Bug: 65213616)
2188 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002189 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002190 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002191 continue
2192
Tom Cherryd14b8952018-08-09 14:26:00 -07002193 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2194 # filename listed in system.map may contain an additional leading slash
2195 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2196 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002197 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002198 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002199 arcname = entry.lstrip('/')
2200 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002201 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002202 else:
2203 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002204
2205 assert arcname in input_zip.namelist(), \
2206 "Failed to find the ZIP entry for {}".format(entry)
2207
Tao Baoc765cca2018-01-31 17:32:40 -08002208 info = input_zip.getinfo(arcname)
2209 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002210
2211 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002212 # image, check the original block list to determine its completeness. Note
2213 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002214 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002215 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002216
Tao Baoc765cca2018-01-31 17:32:40 -08002217 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2218 ranges.extra['incomplete'] = True
2219
2220 return image
2221
2222
Doug Zongkereef39442009-04-02 12:14:19 -07002223def GetKeyPasswords(keylist):
2224 """Given a list of keys, prompt the user to enter passwords for
2225 those which require them. Return a {key: password} dict. password
2226 will be None if the key has no password."""
2227
Doug Zongker8ce7c252009-05-22 13:34:54 -07002228 no_passwords = []
2229 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002230 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002231 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002232
2233 # sorted() can't compare strings to None, so convert Nones to strings
2234 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002235 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002236 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002237 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002238 continue
2239
T.R. Fullhart37e10522013-03-18 10:31:26 -07002240 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002241 "-inform", "DER", "-nocrypt"],
2242 stdin=devnull.fileno(),
2243 stdout=devnull.fileno(),
2244 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002245 p.communicate()
2246 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002247 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002248 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002249 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002250 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2251 "-inform", "DER", "-passin", "pass:"],
2252 stdin=devnull.fileno(),
2253 stdout=devnull.fileno(),
2254 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002255 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002256 if p.returncode == 0:
2257 # Encrypted key with empty string as password.
2258 key_passwords[k] = ''
2259 elif stderr.startswith('Error decrypting key'):
2260 # Definitely encrypted key.
2261 # It would have said "Error reading key" if it didn't parse correctly.
2262 need_passwords.append(k)
2263 else:
2264 # Potentially, a type of key that openssl doesn't understand.
2265 # We'll let the routines in signapk.jar handle it.
2266 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002267 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002268
T.R. Fullhart37e10522013-03-18 10:31:26 -07002269 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002270 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002271 return key_passwords
2272
2273
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002274def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002275 """Gets the minSdkVersion declared in the APK.
2276
Martin Stjernholm58472e82022-01-07 22:08:47 +00002277 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2278 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002279
2280 Args:
2281 apk_name: The APK filename.
2282
2283 Returns:
2284 The parsed SDK version string.
2285
2286 Raises:
2287 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002288 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002289 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002290 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002291 stderr=subprocess.PIPE)
2292 stdoutdata, stderrdata = proc.communicate()
2293 if proc.returncode != 0:
2294 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002295 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2296 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002297
Tao Baof47bf0f2018-03-21 23:28:51 -07002298 for line in stdoutdata.split("\n"):
2299 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002300 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2301 if m:
2302 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002303 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002304
2305
2306def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002307 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002308
Tao Baof47bf0f2018-03-21 23:28:51 -07002309 If minSdkVersion is set to a codename, it is translated to a number using the
2310 provided map.
2311
2312 Args:
2313 apk_name: The APK filename.
2314
2315 Returns:
2316 The parsed SDK version number.
2317
2318 Raises:
2319 ExternalError: On failing to get the min SDK version number.
2320 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002321 version = GetMinSdkVersion(apk_name)
2322 try:
2323 return int(version)
2324 except ValueError:
2325 # Not a decimal number. Codename?
2326 if version in codename_to_api_level_map:
2327 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002328 raise ExternalError(
2329 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2330 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002331
2332
2333def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002334 codename_to_api_level_map=None, whole_file=False,
2335 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002336 """Sign the input_name zip/jar/apk, producing output_name. Use the
2337 given key and password (the latter may be None if the key does not
2338 have a password.
2339
Doug Zongker951495f2009-08-14 12:44:19 -07002340 If whole_file is true, use the "-w" option to SignApk to embed a
2341 signature that covers the whole file in the archive comment of the
2342 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002343
2344 min_api_level is the API Level (int) of the oldest platform this file may end
2345 up on. If not specified for an APK, the API Level is obtained by interpreting
2346 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2347
2348 codename_to_api_level_map is needed to translate the codename which may be
2349 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002350
2351 Caller may optionally specify extra args to be passed to SignApk, which
2352 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002353 """
Tao Bao76def242017-11-21 09:25:31 -08002354 if codename_to_api_level_map is None:
2355 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002356 if extra_signapk_args is None:
2357 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002358
Alex Klyubin9667b182015-12-10 13:38:50 -08002359 java_library_path = os.path.join(
2360 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2361
Tao Baoe95540e2016-11-08 12:08:53 -08002362 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2363 ["-Djava.library.path=" + java_library_path,
2364 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002365 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002366 if whole_file:
2367 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002368
2369 min_sdk_version = min_api_level
2370 if min_sdk_version is None:
2371 if not whole_file:
2372 min_sdk_version = GetMinSdkVersionInt(
2373 input_name, codename_to_api_level_map)
2374 if min_sdk_version is not None:
2375 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2376
T.R. Fullhart37e10522013-03-18 10:31:26 -07002377 cmd.extend([key + OPTIONS.public_key_suffix,
2378 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002379 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002380
Tao Bao73dd4f42018-10-04 16:25:33 -07002381 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002382 if password is not None:
2383 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002384 stdoutdata, _ = proc.communicate(password)
2385 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002386 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002387 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002388 proc.returncode, stdoutdata))
2389
Doug Zongkereef39442009-04-02 12:14:19 -07002390
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002391def SignSePolicy(sepolicy, key, password):
2392 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2393 an RSA .sig signature files.
2394 """
2395
2396 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002397 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002398 return False
2399
2400 java_library_path = os.path.join(
2401 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2402
2403 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002404 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002405 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002406 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002407
2408 cmd.extend([key + OPTIONS.public_key_suffix,
2409 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002410 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002411
2412 proc = Run(cmd, stdin=subprocess.PIPE)
2413 if password is not None:
2414 password += "\n"
2415 stdoutdata, _ = proc.communicate(password)
2416 if proc.returncode != 0:
2417 raise ExternalError(
2418 "Failed to run sign sepolicy: return code {}:\n{}".format(
2419 proc.returncode, stdoutdata))
2420 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002421
Kelvin Zhangf294c872022-10-06 14:21:36 -07002422
Doug Zongker37974732010-09-16 17:44:38 -07002423def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002424 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002425
Tao Bao9dd909e2017-11-14 11:27:32 -08002426 For non-AVB images, raise exception if the data is too big. Print a warning
2427 if the data is nearing the maximum size.
2428
2429 For AVB images, the actual image size should be identical to the limit.
2430
2431 Args:
2432 data: A string that contains all the data for the partition.
2433 target: The partition name. The ".img" suffix is optional.
2434 info_dict: The dict to be looked up for relevant info.
2435 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002436 if target.endswith(".img"):
2437 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002438 mount_point = "/" + target
2439
Ying Wangf8824af2014-06-03 14:07:27 -07002440 fs_type = None
2441 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002442 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002443 if mount_point == "/userdata":
2444 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002445 p = info_dict["fstab"][mount_point]
2446 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002447 device = p.device
2448 if "/" in device:
2449 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002450 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002451 if not fs_type or not limit:
2452 return
Doug Zongkereef39442009-04-02 12:14:19 -07002453
Andrew Boie0f9aec82012-02-14 09:32:52 -08002454 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002455 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2456 # path.
2457 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2458 if size != limit:
2459 raise ExternalError(
2460 "Mismatching image size for %s: expected %d actual %d" % (
2461 target, limit, size))
2462 else:
2463 pct = float(size) * 100.0 / limit
2464 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2465 if pct >= 99.0:
2466 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002467
2468 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002469 logger.warning("\n WARNING: %s\n", msg)
2470 else:
2471 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002472
2473
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002474def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002475 """Parses the APK certs info from a given target-files zip.
2476
2477 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2478 tuple with the following elements: (1) a dictionary that maps packages to
2479 certs (based on the "certificate" and "private_key" attributes in the file;
2480 (2) a string representing the extension of compressed APKs in the target files
2481 (e.g ".gz", ".bro").
2482
2483 Args:
2484 tf_zip: The input target_files ZipFile (already open).
2485
2486 Returns:
2487 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2488 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2489 no compressed APKs.
2490 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002491 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002492 compressed_extension = None
2493
Tao Bao0f990332017-09-08 19:02:54 -07002494 # META/apkcerts.txt contains the info for _all_ the packages known at build
2495 # time. Filter out the ones that are not installed.
2496 installed_files = set()
2497 for name in tf_zip.namelist():
2498 basename = os.path.basename(name)
2499 if basename:
2500 installed_files.add(basename)
2501
Tao Baoda30cfa2017-12-01 16:19:46 -08002502 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002503 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002504 if not line:
2505 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002506 m = re.match(
2507 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002508 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2509 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002510 line)
2511 if not m:
2512 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002513
Tao Bao818ddf52018-01-05 11:17:34 -08002514 matches = m.groupdict()
2515 cert = matches["CERT"]
2516 privkey = matches["PRIVKEY"]
2517 name = matches["NAME"]
2518 this_compressed_extension = matches["COMPRESSED"]
2519
2520 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2521 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2522 if cert in SPECIAL_CERT_STRINGS and not privkey:
2523 certmap[name] = cert
2524 elif (cert.endswith(OPTIONS.public_key_suffix) and
2525 privkey.endswith(OPTIONS.private_key_suffix) and
2526 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2527 certmap[name] = cert[:-public_key_suffix_len]
2528 else:
2529 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2530
2531 if not this_compressed_extension:
2532 continue
2533
2534 # Only count the installed files.
2535 filename = name + '.' + this_compressed_extension
2536 if filename not in installed_files:
2537 continue
2538
2539 # Make sure that all the values in the compression map have the same
2540 # extension. We don't support multiple compression methods in the same
2541 # system image.
2542 if compressed_extension:
2543 if this_compressed_extension != compressed_extension:
2544 raise ValueError(
2545 "Multiple compressed extensions: {} vs {}".format(
2546 compressed_extension, this_compressed_extension))
2547 else:
2548 compressed_extension = this_compressed_extension
2549
2550 return (certmap,
2551 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002552
2553
Doug Zongkereef39442009-04-02 12:14:19 -07002554COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002555Global options
2556
2557 -p (--path) <dir>
2558 Prepend <dir>/bin to the list of places to search for binaries run by this
2559 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002560
Doug Zongker05d3dea2009-06-22 11:32:31 -07002561 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002562 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002563
Tao Bao30df8b42018-04-23 15:32:53 -07002564 -x (--extra) <key=value>
2565 Add a key/value pair to the 'extras' dict, which device-specific extension
2566 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002567
Doug Zongkereef39442009-04-02 12:14:19 -07002568 -v (--verbose)
2569 Show command lines being executed.
2570
2571 -h (--help)
2572 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002573
2574 --logfile <file>
2575 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002576"""
2577
Kelvin Zhang0876c412020-06-23 15:06:58 -04002578
Doug Zongkereef39442009-04-02 12:14:19 -07002579def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002580 print(docstring.rstrip("\n"))
2581 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002582
2583
2584def ParseOptions(argv,
2585 docstring,
2586 extra_opts="", extra_long_opts=(),
2587 extra_option_handler=None):
2588 """Parse the options in argv and return any arguments that aren't
2589 flags. docstring is the calling module's docstring, to be displayed
2590 for errors and -h. extra_opts and extra_long_opts are for flags
2591 defined by the caller, which are processed by passing them to
2592 extra_option_handler."""
2593
2594 try:
2595 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002596 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002597 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002598 "signapk_shared_library_path=", "extra_signapk_args=",
2599 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002600 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002601 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2602 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002603 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002604 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002605 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002606 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002607 sys.exit(2)
2608
Doug Zongkereef39442009-04-02 12:14:19 -07002609 for o, a in opts:
2610 if o in ("-h", "--help"):
2611 Usage(docstring)
2612 sys.exit()
2613 elif o in ("-v", "--verbose"):
2614 OPTIONS.verbose = True
2615 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002616 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002617 elif o in ("--signapk_path",):
2618 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002619 elif o in ("--signapk_shared_library_path",):
2620 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002621 elif o in ("--extra_signapk_args",):
2622 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002623 elif o in ("--sign_sepolicy_path",):
2624 OPTIONS.sign_sepolicy_path = a
2625 elif o in ("--extra_sign_sepolicy_args",):
2626 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002627 elif o in ("--aapt2_path",):
2628 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002629 elif o in ("--java_path",):
2630 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002631 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002632 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002633 elif o in ("--android_jar_path",):
2634 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002635 elif o in ("--public_key_suffix",):
2636 OPTIONS.public_key_suffix = a
2637 elif o in ("--private_key_suffix",):
2638 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002639 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002640 raise ValueError(
2641 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002642 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002643 raise ValueError(
2644 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002645 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002646 raise ValueError(
2647 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002648 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002649 raise ValueError(
2650 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002651 elif o in ("-s", "--device_specific"):
2652 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002653 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002654 key, value = a.split("=", 1)
2655 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002656 elif o in ("--logfile",):
2657 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002658 else:
2659 if extra_option_handler is None or not extra_option_handler(o, a):
2660 assert False, "unknown option \"%s\"" % (o,)
2661
Doug Zongker85448772014-09-09 14:59:20 -07002662 if OPTIONS.search_path:
2663 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2664 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002665
2666 return args
2667
2668
Tao Bao4c851b12016-09-19 13:54:38 -07002669def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002670 """Make a temp file and add it to the list of things to be deleted
2671 when Cleanup() is called. Return the filename."""
2672 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2673 os.close(fd)
2674 OPTIONS.tempfiles.append(fn)
2675 return fn
2676
2677
Tao Bao1c830bf2017-12-25 10:43:47 -08002678def MakeTempDir(prefix='tmp', suffix=''):
2679 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2680
2681 Returns:
2682 The absolute pathname of the new directory.
2683 """
2684 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2685 OPTIONS.tempfiles.append(dir_name)
2686 return dir_name
2687
2688
Doug Zongkereef39442009-04-02 12:14:19 -07002689def Cleanup():
2690 for i in OPTIONS.tempfiles:
2691 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002692 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002693 else:
2694 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002695 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002696
2697
2698class PasswordManager(object):
2699 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002700 self.editor = os.getenv("EDITOR")
2701 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002702
2703 def GetPasswords(self, items):
2704 """Get passwords corresponding to each string in 'items',
2705 returning a dict. (The dict may have keys in addition to the
2706 values in 'items'.)
2707
2708 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2709 user edit that file to add more needed passwords. If no editor is
2710 available, or $ANDROID_PW_FILE isn't define, prompts the user
2711 interactively in the ordinary way.
2712 """
2713
2714 current = self.ReadFile()
2715
2716 first = True
2717 while True:
2718 missing = []
2719 for i in items:
2720 if i not in current or not current[i]:
2721 missing.append(i)
2722 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002723 if not missing:
2724 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002725
2726 for i in missing:
2727 current[i] = ""
2728
2729 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002730 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002731 if sys.version_info[0] >= 3:
2732 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002733 answer = raw_input("try to edit again? [y]> ").strip()
2734 if answer and answer[0] not in 'yY':
2735 raise RuntimeError("key passwords unavailable")
2736 first = False
2737
2738 current = self.UpdateAndReadFile(current)
2739
Kelvin Zhang0876c412020-06-23 15:06:58 -04002740 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002741 """Prompt the user to enter a value (password) for each key in
2742 'current' whose value is fales. Returns a new dict with all the
2743 values.
2744 """
2745 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002746 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002747 if v:
2748 result[k] = v
2749 else:
2750 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002751 result[k] = getpass.getpass(
2752 "Enter password for %s key> " % k).strip()
2753 if result[k]:
2754 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002755 return result
2756
2757 def UpdateAndReadFile(self, current):
2758 if not self.editor or not self.pwfile:
2759 return self.PromptResult(current)
2760
2761 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002762 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002763 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2764 f.write("# (Additional spaces are harmless.)\n\n")
2765
2766 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002767 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002768 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002769 f.write("[[[ %s ]]] %s\n" % (v, k))
2770 if not v and first_line is None:
2771 # position cursor on first line with no password.
2772 first_line = i + 4
2773 f.close()
2774
Tao Bao986ee862018-10-04 15:46:16 -07002775 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002776
2777 return self.ReadFile()
2778
2779 def ReadFile(self):
2780 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002781 if self.pwfile is None:
2782 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002783 try:
2784 f = open(self.pwfile, "r")
2785 for line in f:
2786 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002787 if not line or line[0] == '#':
2788 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002789 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2790 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002791 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002792 else:
2793 result[m.group(2)] = m.group(1)
2794 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002795 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002796 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002797 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002798 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002799
2800
Dan Albert8e0178d2015-01-27 15:53:15 -08002801def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2802 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002803
Dan Albert8e0178d2015-01-27 15:53:15 -08002804 if compress_type is None:
2805 compress_type = zip_file.compression
2806 if arcname is None:
2807 arcname = filename
2808
2809 saved_stat = os.stat(filename)
2810
2811 try:
2812 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2813 # file to be zipped and reset it when we're done.
2814 os.chmod(filename, perms)
2815
2816 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002817 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2818 # intentional. zip stores datetimes in local time without a time zone
2819 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2820 # in the zip archive.
2821 local_epoch = datetime.datetime.fromtimestamp(0)
2822 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002823 os.utime(filename, (timestamp, timestamp))
2824
2825 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2826 finally:
2827 os.chmod(filename, saved_stat.st_mode)
2828 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Dan Albert8e0178d2015-01-27 15:53:15 -08002829
2830
Tao Bao58c1b962015-05-20 09:32:18 -07002831def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002832 compress_type=None):
2833 """Wrap zipfile.writestr() function to work around the zip64 limit.
2834
Kelvin Zhang37a42902022-10-26 12:49:03 -07002835 Python's zip implementation won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07002836 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2837 when calling crc32(bytes).
2838
2839 But it still works fine to write a shorter string into a large zip file.
2840 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2841 when we know the string won't be too long.
2842 """
2843
Tao Baof3282b42015-04-01 11:21:55 -07002844 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2845 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002846 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002847 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002848 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002849 else:
Tao Baof3282b42015-04-01 11:21:55 -07002850 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002851 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2852 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2853 # such a case (since
2854 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2855 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2856 # permission bits. We follow the logic in Python 3 to get consistent
2857 # behavior between using the two versions.
2858 if not zinfo.external_attr:
2859 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002860
2861 # If compress_type is given, it overrides the value in zinfo.
2862 if compress_type is not None:
2863 zinfo.compress_type = compress_type
2864
Tao Bao58c1b962015-05-20 09:32:18 -07002865 # If perms is given, it has a priority.
2866 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002867 # If perms doesn't set the file type, mark it as a regular file.
2868 if perms & 0o770000 == 0:
2869 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002870 zinfo.external_attr = perms << 16
2871
Tao Baof3282b42015-04-01 11:21:55 -07002872 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002873 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2874
Dan Albert8b72aef2015-03-23 19:13:21 -07002875 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002876
2877
Kelvin Zhang1caead02022-09-23 10:06:03 -07002878def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08002879 """Deletes entries from a ZIP file.
2880
Tao Bao89d7ab22017-12-14 17:05:33 -08002881 Args:
2882 zip_filename: The name of the ZIP file.
2883 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08002884 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002885 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002886 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002887 # If list is empty, nothing to do
2888 if not entries:
2889 return
Wei Li8895f9e2022-10-10 17:13:17 -07002890
2891 with zipfile.ZipFile(zip_filename, 'r') as zin:
2892 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
2893 raise ExternalError(
2894 "Failed to delete zip entries, name not matched: %s" % entries)
2895
2896 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
2897 os.close(fd)
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -08002898 cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
2899 for entry in entries:
2900 cmd.append("-x")
2901 cmd.append(entry)
2902 RunAndCheckOutput(cmd)
Wei Li8895f9e2022-10-10 17:13:17 -07002903
Wei Li8895f9e2022-10-10 17:13:17 -07002904
2905 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08002906
2907
Doug Zongker05d3dea2009-06-22 11:32:31 -07002908class DeviceSpecificParams(object):
2909 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002910
Doug Zongker05d3dea2009-06-22 11:32:31 -07002911 def __init__(self, **kwargs):
2912 """Keyword arguments to the constructor become attributes of this
2913 object, which is passed to all functions in the device-specific
2914 module."""
Tao Bao38884282019-07-10 22:20:56 -07002915 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002916 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002917 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002918
2919 if self.module is None:
2920 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002921 if not path:
2922 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002923 try:
2924 if os.path.isdir(path):
2925 info = imp.find_module("releasetools", [path])
2926 else:
2927 d, f = os.path.split(path)
2928 b, x = os.path.splitext(f)
2929 if x == ".py":
2930 f = b
2931 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002932 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002933 self.module = imp.load_module("device_specific", *info)
2934 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002935 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002936
2937 def _DoCall(self, function_name, *args, **kwargs):
2938 """Call the named function in the device-specific module, passing
2939 the given args and kwargs. The first argument to the call will be
2940 the DeviceSpecific object itself. If there is no module, or the
2941 module does not define the function, return the value of the
2942 'default' kwarg (which itself defaults to None)."""
2943 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002944 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002945 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2946
2947 def FullOTA_Assertions(self):
2948 """Called after emitting the block of assertions at the top of a
2949 full OTA package. Implementations can add whatever additional
2950 assertions they like."""
2951 return self._DoCall("FullOTA_Assertions")
2952
Doug Zongkere5ff5902012-01-17 10:55:37 -08002953 def FullOTA_InstallBegin(self):
2954 """Called at the start of full OTA installation."""
2955 return self._DoCall("FullOTA_InstallBegin")
2956
Yifan Hong10c530d2018-12-27 17:34:18 -08002957 def FullOTA_GetBlockDifferences(self):
2958 """Called during full OTA installation and verification.
2959 Implementation should return a list of BlockDifference objects describing
2960 the update on each additional partitions.
2961 """
2962 return self._DoCall("FullOTA_GetBlockDifferences")
2963
Doug Zongker05d3dea2009-06-22 11:32:31 -07002964 def FullOTA_InstallEnd(self):
2965 """Called at the end of full OTA installation; typically this is
2966 used to install the image for the device's baseband processor."""
2967 return self._DoCall("FullOTA_InstallEnd")
2968
2969 def IncrementalOTA_Assertions(self):
2970 """Called after emitting the block of assertions at the top of an
2971 incremental OTA package. Implementations can add whatever
2972 additional assertions they like."""
2973 return self._DoCall("IncrementalOTA_Assertions")
2974
Doug Zongkere5ff5902012-01-17 10:55:37 -08002975 def IncrementalOTA_VerifyBegin(self):
2976 """Called at the start of the verification phase of incremental
2977 OTA installation; additional checks can be placed here to abort
2978 the script before any changes are made."""
2979 return self._DoCall("IncrementalOTA_VerifyBegin")
2980
Doug Zongker05d3dea2009-06-22 11:32:31 -07002981 def IncrementalOTA_VerifyEnd(self):
2982 """Called at the end of the verification phase of incremental OTA
2983 installation; additional checks can be placed here to abort the
2984 script before any changes are made."""
2985 return self._DoCall("IncrementalOTA_VerifyEnd")
2986
Doug Zongkere5ff5902012-01-17 10:55:37 -08002987 def IncrementalOTA_InstallBegin(self):
2988 """Called at the start of incremental OTA installation (after
2989 verification is complete)."""
2990 return self._DoCall("IncrementalOTA_InstallBegin")
2991
Yifan Hong10c530d2018-12-27 17:34:18 -08002992 def IncrementalOTA_GetBlockDifferences(self):
2993 """Called during incremental OTA installation and verification.
2994 Implementation should return a list of BlockDifference objects describing
2995 the update on each additional partitions.
2996 """
2997 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2998
Doug Zongker05d3dea2009-06-22 11:32:31 -07002999 def IncrementalOTA_InstallEnd(self):
3000 """Called at the end of incremental OTA installation; typically
3001 this is used to install the image for the device's baseband
3002 processor."""
3003 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003004
Tao Bao9bc6bb22015-11-09 16:58:28 -08003005 def VerifyOTA_Assertions(self):
3006 return self._DoCall("VerifyOTA_Assertions")
3007
Tao Bao76def242017-11-21 09:25:31 -08003008
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003009class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003010 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003011 self.name = name
3012 self.data = data
3013 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003014 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003015 self.sha1 = sha1(data).hexdigest()
3016
3017 @classmethod
3018 def FromLocalFile(cls, name, diskname):
3019 f = open(diskname, "rb")
3020 data = f.read()
3021 f.close()
3022 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003023
3024 def WriteToTemp(self):
3025 t = tempfile.NamedTemporaryFile()
3026 t.write(self.data)
3027 t.flush()
3028 return t
3029
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003030 def WriteToDir(self, d):
3031 with open(os.path.join(d, self.name), "wb") as fp:
3032 fp.write(self.data)
3033
Geremy Condra36bd3652014-02-06 19:45:10 -08003034 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003035 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003036
Tao Bao76def242017-11-21 09:25:31 -08003037
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003038DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003039 ".gz": "imgdiff",
3040 ".zip": ["imgdiff", "-z"],
3041 ".jar": ["imgdiff", "-z"],
3042 ".apk": ["imgdiff", "-z"],
3043 ".img": "imgdiff",
3044}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003045
Tao Bao76def242017-11-21 09:25:31 -08003046
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003047class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003048 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003049 self.tf = tf
3050 self.sf = sf
3051 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003052 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003053
3054 def ComputePatch(self):
3055 """Compute the patch (as a string of data) needed to turn sf into
3056 tf. Returns the same tuple as GetPatch()."""
3057
3058 tf = self.tf
3059 sf = self.sf
3060
Doug Zongker24cd2802012-08-14 16:36:15 -07003061 if self.diff_program:
3062 diff_program = self.diff_program
3063 else:
3064 ext = os.path.splitext(tf.name)[1]
3065 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003066
3067 ttemp = tf.WriteToTemp()
3068 stemp = sf.WriteToTemp()
3069
3070 ext = os.path.splitext(tf.name)[1]
3071
3072 try:
3073 ptemp = tempfile.NamedTemporaryFile()
3074 if isinstance(diff_program, list):
3075 cmd = copy.copy(diff_program)
3076 else:
3077 cmd = [diff_program]
3078 cmd.append(stemp.name)
3079 cmd.append(ttemp.name)
3080 cmd.append(ptemp.name)
3081 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003082 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003083
Doug Zongkerf8340082014-08-05 10:39:37 -07003084 def run():
3085 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003086 if e:
3087 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003088 th = threading.Thread(target=run)
3089 th.start()
3090 th.join(timeout=300) # 5 mins
3091 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003092 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003093 p.terminate()
3094 th.join(5)
3095 if th.is_alive():
3096 p.kill()
3097 th.join()
3098
Tianjie Xua2a9f992018-01-05 15:15:54 -08003099 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003100 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003101 self.patch = None
3102 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003103 diff = ptemp.read()
3104 finally:
3105 ptemp.close()
3106 stemp.close()
3107 ttemp.close()
3108
3109 self.patch = diff
3110 return self.tf, self.sf, self.patch
3111
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003112 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003113 """Returns a tuple of (target_file, source_file, patch_data).
3114
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003115 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003116 computing the patch failed.
3117 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003118 return self.tf, self.sf, self.patch
3119
3120
3121def ComputeDifferences(diffs):
3122 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003123 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003124
3125 # Do the largest files first, to try and reduce the long-pole effect.
3126 by_size = [(i.tf.size, i) for i in diffs]
3127 by_size.sort(reverse=True)
3128 by_size = [i[1] for i in by_size]
3129
3130 lock = threading.Lock()
3131 diff_iter = iter(by_size) # accessed under lock
3132
3133 def worker():
3134 try:
3135 lock.acquire()
3136 for d in diff_iter:
3137 lock.release()
3138 start = time.time()
3139 d.ComputePatch()
3140 dur = time.time() - start
3141 lock.acquire()
3142
3143 tf, sf, patch = d.GetPatch()
3144 if sf.name == tf.name:
3145 name = tf.name
3146 else:
3147 name = "%s (%s)" % (tf.name, sf.name)
3148 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003149 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003150 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003151 logger.info(
3152 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3153 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003154 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003155 except Exception:
3156 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003157 raise
3158
3159 # start worker threads; wait for them all to finish.
3160 threads = [threading.Thread(target=worker)
3161 for i in range(OPTIONS.worker_threads)]
3162 for th in threads:
3163 th.start()
3164 while threads:
3165 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003166
3167
Dan Albert8b72aef2015-03-23 19:13:21 -07003168class BlockDifference(object):
3169 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003170 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003171 self.tgt = tgt
3172 self.src = src
3173 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003174 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003175 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003176
Tao Baodd2a5892015-03-12 12:32:37 -07003177 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003178 version = max(
3179 int(i) for i in
3180 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003181 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003182 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003183
Tianjie Xu41976c72019-07-03 13:57:01 -07003184 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3185 version=self.version,
3186 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003187 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003188 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003189 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003190 self.touched_src_ranges = b.touched_src_ranges
3191 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003192
Yifan Hong10c530d2018-12-27 17:34:18 -08003193 # On devices with dynamic partitions, for new partitions,
3194 # src is None but OPTIONS.source_info_dict is not.
3195 if OPTIONS.source_info_dict is None:
3196 is_dynamic_build = OPTIONS.info_dict.get(
3197 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003198 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003199 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003200 is_dynamic_build = OPTIONS.source_info_dict.get(
3201 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003202 is_dynamic_source = partition in shlex.split(
3203 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003204
Yifan Hongbb2658d2019-01-25 12:30:58 -08003205 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003206 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3207
Yifan Hongbb2658d2019-01-25 12:30:58 -08003208 # For dynamic partitions builds, check partition list in both source
3209 # and target build because new partitions may be added, and existing
3210 # partitions may be removed.
3211 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3212
Yifan Hong10c530d2018-12-27 17:34:18 -08003213 if is_dynamic:
3214 self.device = 'map_partition("%s")' % partition
3215 else:
3216 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003217 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3218 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003219 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003220 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3221 OPTIONS.source_info_dict)
3222 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003223
Tao Baod8d14be2016-02-04 14:26:02 -08003224 @property
3225 def required_cache(self):
3226 return self._required_cache
3227
Tao Bao76def242017-11-21 09:25:31 -08003228 def WriteScript(self, script, output_zip, progress=None,
3229 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003230 if not self.src:
3231 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003232 script.Print("Patching %s image unconditionally..." % (self.partition,))
3233 else:
3234 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003235
Dan Albert8b72aef2015-03-23 19:13:21 -07003236 if progress:
3237 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003238 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003239
3240 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003241 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003242
Tao Bao9bc6bb22015-11-09 16:58:28 -08003243 def WriteStrictVerifyScript(self, script):
3244 """Verify all the blocks in the care_map, including clobbered blocks.
3245
3246 This differs from the WriteVerifyScript() function: a) it prints different
3247 error messages; b) it doesn't allow half-way updated images to pass the
3248 verification."""
3249
3250 partition = self.partition
3251 script.Print("Verifying %s..." % (partition,))
3252 ranges = self.tgt.care_map
3253 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003254 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003255 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3256 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003257 self.device, ranges_str,
3258 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003259 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003260 script.AppendExtra("")
3261
Tao Baod522bdc2016-04-12 15:53:16 -07003262 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003263 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003264
3265 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003266 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003267 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003268
3269 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003270 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003271 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003272 ranges = self.touched_src_ranges
3273 expected_sha1 = self.touched_src_sha1
3274 else:
3275 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3276 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003277
3278 # No blocks to be checked, skipping.
3279 if not ranges:
3280 return
3281
Tao Bao5ece99d2015-05-12 11:42:31 -07003282 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003283 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003284 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003285 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3286 '"%s.patch.dat")) then' % (
3287 self.device, ranges_str, expected_sha1,
3288 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003289 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003290 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003291
Tianjie Xufc3422a2015-12-15 11:53:59 -08003292 if self.version >= 4:
3293
3294 # Bug: 21124327
3295 # When generating incrementals for the system and vendor partitions in
3296 # version 4 or newer, explicitly check the first block (which contains
3297 # the superblock) of the partition to see if it's what we expect. If
3298 # this check fails, give an explicit log message about the partition
3299 # having been remounted R/W (the most likely explanation).
3300 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003301 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003302
3303 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003304 if partition == "system":
3305 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3306 else:
3307 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003308 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003309 'ifelse (block_image_recover({device}, "{ranges}") && '
3310 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003311 'package_extract_file("{partition}.transfer.list"), '
3312 '"{partition}.new.dat", "{partition}.patch.dat"), '
3313 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003314 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003315 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003316 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003317
Tao Baodd2a5892015-03-12 12:32:37 -07003318 # Abort the OTA update. Note that the incremental OTA cannot be applied
3319 # even if it may match the checksum of the target partition.
3320 # a) If version < 3, operations like move and erase will make changes
3321 # unconditionally and damage the partition.
3322 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003323 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003324 if partition == "system":
3325 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3326 else:
3327 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3328 script.AppendExtra((
3329 'abort("E%d: %s partition has unexpected contents");\n'
3330 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003331
Yifan Hong10c530d2018-12-27 17:34:18 -08003332 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003333 partition = self.partition
3334 script.Print('Verifying the updated %s image...' % (partition,))
3335 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3336 ranges = self.tgt.care_map
3337 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003338 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003339 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003340 self.device, ranges_str,
3341 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003342
3343 # Bug: 20881595
3344 # Verify that extended blocks are really zeroed out.
3345 if self.tgt.extended:
3346 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003347 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003348 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003349 self.device, ranges_str,
3350 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003351 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003352 if partition == "system":
3353 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3354 else:
3355 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003356 script.AppendExtra(
3357 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003358 ' abort("E%d: %s partition has unexpected non-zero contents after '
3359 'OTA update");\n'
3360 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003361 else:
3362 script.Print('Verified the updated %s image.' % (partition,))
3363
Tianjie Xu209db462016-05-24 17:34:52 -07003364 if partition == "system":
3365 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3366 else:
3367 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3368
Tao Bao5fcaaef2015-06-01 13:40:49 -07003369 script.AppendExtra(
3370 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003371 ' abort("E%d: %s partition has unexpected contents after OTA '
3372 'update");\n'
3373 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003374
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003375 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003376 ZipWrite(output_zip,
3377 '{}.transfer.list'.format(self.path),
3378 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003379
Tao Bao76def242017-11-21 09:25:31 -08003380 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3381 # its size. Quailty 9 almost triples the compression time but doesn't
3382 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003383 # zip | brotli(quality 6) | brotli(quality 9)
3384 # compressed_size: 942M | 869M (~8% reduced) | 854M
3385 # compression_time: 75s | 265s | 719s
3386 # decompression_time: 15s | 25s | 25s
3387
3388 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003389 brotli_cmd = ['brotli', '--quality=6',
3390 '--output={}.new.dat.br'.format(self.path),
3391 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003392 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003393 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003394
3395 new_data_name = '{}.new.dat.br'.format(self.partition)
3396 ZipWrite(output_zip,
3397 '{}.new.dat.br'.format(self.path),
3398 new_data_name,
3399 compress_type=zipfile.ZIP_STORED)
3400 else:
3401 new_data_name = '{}.new.dat'.format(self.partition)
3402 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3403
Dan Albert8e0178d2015-01-27 15:53:15 -08003404 ZipWrite(output_zip,
3405 '{}.patch.dat'.format(self.path),
3406 '{}.patch.dat'.format(self.partition),
3407 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003408
Tianjie Xu209db462016-05-24 17:34:52 -07003409 if self.partition == "system":
3410 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3411 else:
3412 code = ErrorCode.VENDOR_UPDATE_FAILURE
3413
Yifan Hong10c530d2018-12-27 17:34:18 -08003414 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003415 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003416 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003417 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003418 device=self.device, partition=self.partition,
3419 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003420 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003421
Kelvin Zhang0876c412020-06-23 15:06:58 -04003422 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003423 data = source.ReadRangeSet(ranges)
3424 ctx = sha1()
3425
3426 for p in data:
3427 ctx.update(p)
3428
3429 return ctx.hexdigest()
3430
Kelvin Zhang0876c412020-06-23 15:06:58 -04003431 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003432 """Return the hash value for all zero blocks."""
3433 zero_block = '\x00' * 4096
3434 ctx = sha1()
3435 for _ in range(num_blocks):
3436 ctx.update(zero_block)
3437
3438 return ctx.hexdigest()
3439
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003440
Tianjie Xu41976c72019-07-03 13:57:01 -07003441# Expose these two classes to support vendor-specific scripts
3442DataImage = images.DataImage
3443EmptyImage = images.EmptyImage
3444
Tao Bao76def242017-11-21 09:25:31 -08003445
Doug Zongker96a57e72010-09-26 14:57:41 -07003446# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003447PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003448 "ext4": "EMMC",
3449 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003450 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003451 "squashfs": "EMMC",
3452 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003453}
Doug Zongker96a57e72010-09-26 14:57:41 -07003454
Kelvin Zhang0876c412020-06-23 15:06:58 -04003455
Yifan Hongbdb32012020-05-07 12:38:53 -07003456def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3457 """
3458 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3459 backwards compatibility. It aborts if the fstab entry has slotselect option
3460 (unless check_no_slot is explicitly set to False).
3461 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003462 fstab = info["fstab"]
3463 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003464 if check_no_slot:
3465 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003466 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003467 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3468 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003469 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003470
3471
Yifan Hongbdb32012020-05-07 12:38:53 -07003472def GetTypeAndDeviceExpr(mount_point, info):
3473 """
3474 Return the filesystem of the partition, and an edify expression that evaluates
3475 to the device at runtime.
3476 """
3477 fstab = info["fstab"]
3478 if fstab:
3479 p = fstab[mount_point]
3480 device_expr = '"%s"' % fstab[mount_point].device
3481 if p.slotselect:
3482 device_expr = 'add_slot_suffix(%s)' % device_expr
3483 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003484 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003485
3486
3487def GetEntryForDevice(fstab, device):
3488 """
3489 Returns:
3490 The first entry in fstab whose device is the given value.
3491 """
3492 if not fstab:
3493 return None
3494 for mount_point in fstab:
3495 if fstab[mount_point].device == device:
3496 return fstab[mount_point]
3497 return None
3498
Kelvin Zhang0876c412020-06-23 15:06:58 -04003499
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003500def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003501 """Parses and converts a PEM-encoded certificate into DER-encoded.
3502
3503 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3504
3505 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003506 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003507 """
3508 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003509 save = False
3510 for line in data.split("\n"):
3511 if "--END CERTIFICATE--" in line:
3512 break
3513 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003514 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003515 if "--BEGIN CERTIFICATE--" in line:
3516 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003517 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003518 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003519
Tao Bao04e1f012018-02-04 12:13:35 -08003520
3521def ExtractPublicKey(cert):
3522 """Extracts the public key (PEM-encoded) from the given certificate file.
3523
3524 Args:
3525 cert: The certificate filename.
3526
3527 Returns:
3528 The public key string.
3529
3530 Raises:
3531 AssertionError: On non-zero return from 'openssl'.
3532 """
3533 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3534 # While openssl 1.1 writes the key into the given filename followed by '-out',
3535 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3536 # stdout instead.
3537 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3538 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3539 pubkey, stderrdata = proc.communicate()
3540 assert proc.returncode == 0, \
3541 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3542 return pubkey
3543
3544
Tao Bao1ac886e2019-06-26 11:58:22 -07003545def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003546 """Extracts the AVB public key from the given public or private key.
3547
3548 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003549 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003550 key: The input key file, which should be PEM-encoded public or private key.
3551
3552 Returns:
3553 The path to the extracted AVB public key file.
3554 """
3555 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3556 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003557 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003558 return output
3559
3560
Doug Zongker412c02f2014-02-13 10:58:24 -08003561def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3562 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003563 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003564
Tao Bao6d5d6232018-03-09 17:04:42 -08003565 Most of the space in the boot and recovery images is just the kernel, which is
3566 identical for the two, so the resulting patch should be efficient. Add it to
3567 the output zip, along with a shell script that is run from init.rc on first
3568 boot to actually do the patching and install the new recovery image.
3569
3570 Args:
3571 input_dir: The top-level input directory of the target-files.zip.
3572 output_sink: The callback function that writes the result.
3573 recovery_img: File object for the recovery image.
3574 boot_img: File objects for the boot image.
3575 info_dict: A dict returned by common.LoadInfoDict() on the input
3576 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003577 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003578 if info_dict is None:
3579 info_dict = OPTIONS.info_dict
3580
Tao Bao6d5d6232018-03-09 17:04:42 -08003581 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003582 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3583
3584 if board_uses_vendorimage:
3585 # In this case, the output sink is rooted at VENDOR
3586 recovery_img_path = "etc/recovery.img"
3587 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3588 sh_dir = "bin"
3589 else:
3590 # In this case the output sink is rooted at SYSTEM
3591 recovery_img_path = "vendor/etc/recovery.img"
3592 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3593 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003594
Tao Baof2cffbd2015-07-22 12:33:18 -07003595 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003596 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003597
3598 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003599 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003600 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003601 # With system-root-image, boot and recovery images will have mismatching
3602 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3603 # to handle such a case.
3604 if system_root_image:
3605 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003606 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003607 assert not os.path.exists(path)
3608 else:
3609 diff_program = ["imgdiff"]
3610 if os.path.exists(path):
3611 diff_program.append("-b")
3612 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003613 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003614 else:
3615 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003616
3617 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3618 _, _, patch = d.ComputePatch()
3619 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003620
Dan Albertebb19aa2015-03-27 19:11:53 -07003621 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003622 # The following GetTypeAndDevice()s need to use the path in the target
3623 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003624 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3625 check_no_slot=False)
3626 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3627 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003628 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003629 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003630
Tao Baof2cffbd2015-07-22 12:33:18 -07003631 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003632
3633 # Note that we use /vendor to refer to the recovery resources. This will
3634 # work for a separate vendor partition mounted at /vendor or a
3635 # /system/vendor subdirectory on the system partition, for which init will
3636 # create a symlink from /vendor to /system/vendor.
3637
3638 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003639if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3640 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003641 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003642 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3643 log -t recovery "Installing new recovery image: succeeded" || \\
3644 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003645else
3646 log -t recovery "Recovery image already installed"
3647fi
3648""" % {'type': recovery_type,
3649 'device': recovery_device,
3650 'sha1': recovery_img.sha1,
3651 'size': recovery_img.size}
3652 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003653 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003654if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3655 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003656 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003657 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3658 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3659 log -t recovery "Installing new recovery image: succeeded" || \\
3660 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003661else
3662 log -t recovery "Recovery image already installed"
3663fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003664""" % {'boot_size': boot_img.size,
3665 'boot_sha1': boot_img.sha1,
3666 'recovery_size': recovery_img.size,
3667 'recovery_sha1': recovery_img.sha1,
3668 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003669 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003670 'recovery_type': recovery_type,
3671 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003672 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003673
Bill Peckhame868aec2019-09-17 17:06:47 -07003674 # The install script location moved from /system/etc to /system/bin in the L
3675 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3676 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003677
Tao Bao32fcdab2018-10-12 10:30:39 -07003678 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003679
Tao Baoda30cfa2017-12-01 16:19:46 -08003680 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003681
3682
3683class DynamicPartitionUpdate(object):
3684 def __init__(self, src_group=None, tgt_group=None, progress=None,
3685 block_difference=None):
3686 self.src_group = src_group
3687 self.tgt_group = tgt_group
3688 self.progress = progress
3689 self.block_difference = block_difference
3690
3691 @property
3692 def src_size(self):
3693 if not self.block_difference:
3694 return 0
3695 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3696
3697 @property
3698 def tgt_size(self):
3699 if not self.block_difference:
3700 return 0
3701 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3702
3703 @staticmethod
3704 def _GetSparseImageSize(img):
3705 if not img:
3706 return 0
3707 return img.blocksize * img.total_blocks
3708
3709
3710class DynamicGroupUpdate(object):
3711 def __init__(self, src_size=None, tgt_size=None):
3712 # None: group does not exist. 0: no size limits.
3713 self.src_size = src_size
3714 self.tgt_size = tgt_size
3715
3716
3717class DynamicPartitionsDifference(object):
3718 def __init__(self, info_dict, block_diffs, progress_dict=None,
3719 source_info_dict=None):
3720 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003721 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003722
3723 self._remove_all_before_apply = False
3724 if source_info_dict is None:
3725 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003726 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003727
Tao Baof1113e92019-06-18 12:10:14 -07003728 block_diff_dict = collections.OrderedDict(
3729 [(e.partition, e) for e in block_diffs])
3730
Yifan Hong10c530d2018-12-27 17:34:18 -08003731 assert len(block_diff_dict) == len(block_diffs), \
3732 "Duplicated BlockDifference object for {}".format(
3733 [partition for partition, count in
3734 collections.Counter(e.partition for e in block_diffs).items()
3735 if count > 1])
3736
Yifan Hong79997e52019-01-23 16:56:19 -08003737 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003738
3739 for p, block_diff in block_diff_dict.items():
3740 self._partition_updates[p] = DynamicPartitionUpdate()
3741 self._partition_updates[p].block_difference = block_diff
3742
3743 for p, progress in progress_dict.items():
3744 if p in self._partition_updates:
3745 self._partition_updates[p].progress = progress
3746
3747 tgt_groups = shlex.split(info_dict.get(
3748 "super_partition_groups", "").strip())
3749 src_groups = shlex.split(source_info_dict.get(
3750 "super_partition_groups", "").strip())
3751
3752 for g in tgt_groups:
3753 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003754 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003755 assert p in self._partition_updates, \
3756 "{} is in target super_{}_partition_list but no BlockDifference " \
3757 "object is provided.".format(p, g)
3758 self._partition_updates[p].tgt_group = g
3759
3760 for g in src_groups:
3761 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003762 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003763 assert p in self._partition_updates, \
3764 "{} is in source super_{}_partition_list but no BlockDifference " \
3765 "object is provided.".format(p, g)
3766 self._partition_updates[p].src_group = g
3767
Yifan Hong45433e42019-01-18 13:55:25 -08003768 target_dynamic_partitions = set(shlex.split(info_dict.get(
3769 "dynamic_partition_list", "").strip()))
3770 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3771 if u.tgt_size)
3772 assert block_diffs_with_target == target_dynamic_partitions, \
3773 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3774 list(target_dynamic_partitions), list(block_diffs_with_target))
3775
3776 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3777 "dynamic_partition_list", "").strip()))
3778 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3779 if u.src_size)
3780 assert block_diffs_with_source == source_dynamic_partitions, \
3781 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3782 list(source_dynamic_partitions), list(block_diffs_with_source))
3783
Yifan Hong10c530d2018-12-27 17:34:18 -08003784 if self._partition_updates:
3785 logger.info("Updating dynamic partitions %s",
3786 self._partition_updates.keys())
3787
Yifan Hong79997e52019-01-23 16:56:19 -08003788 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003789
3790 for g in tgt_groups:
3791 self._group_updates[g] = DynamicGroupUpdate()
3792 self._group_updates[g].tgt_size = int(info_dict.get(
3793 "super_%s_group_size" % g, "0").strip())
3794
3795 for g in src_groups:
3796 if g not in self._group_updates:
3797 self._group_updates[g] = DynamicGroupUpdate()
3798 self._group_updates[g].src_size = int(source_info_dict.get(
3799 "super_%s_group_size" % g, "0").strip())
3800
3801 self._Compute()
3802
3803 def WriteScript(self, script, output_zip, write_verify_script=False):
3804 script.Comment('--- Start patching dynamic partitions ---')
3805 for p, u in self._partition_updates.items():
3806 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3807 script.Comment('Patch partition %s' % p)
3808 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3809 write_verify_script=False)
3810
3811 op_list_path = MakeTempFile()
3812 with open(op_list_path, 'w') as f:
3813 for line in self._op_list:
3814 f.write('{}\n'.format(line))
3815
3816 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3817
3818 script.Comment('Update dynamic partition metadata')
3819 script.AppendExtra('assert(update_dynamic_partitions('
3820 'package_extract_file("dynamic_partitions_op_list")));')
3821
3822 if write_verify_script:
3823 for p, u in self._partition_updates.items():
3824 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3825 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003826 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003827
3828 for p, u in self._partition_updates.items():
3829 if u.tgt_size and u.src_size <= u.tgt_size:
3830 script.Comment('Patch partition %s' % p)
3831 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3832 write_verify_script=write_verify_script)
3833 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003834 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003835
3836 script.Comment('--- End patching dynamic partitions ---')
3837
3838 def _Compute(self):
3839 self._op_list = list()
3840
3841 def append(line):
3842 self._op_list.append(line)
3843
3844 def comment(line):
3845 self._op_list.append("# %s" % line)
3846
3847 if self._remove_all_before_apply:
3848 comment('Remove all existing dynamic partitions and groups before '
3849 'applying full OTA')
3850 append('remove_all_groups')
3851
3852 for p, u in self._partition_updates.items():
3853 if u.src_group and not u.tgt_group:
3854 append('remove %s' % p)
3855
3856 for p, u in self._partition_updates.items():
3857 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3858 comment('Move partition %s from %s to default' % (p, u.src_group))
3859 append('move %s default' % p)
3860
3861 for p, u in self._partition_updates.items():
3862 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3863 comment('Shrink partition %s from %d to %d' %
3864 (p, u.src_size, u.tgt_size))
3865 append('resize %s %s' % (p, u.tgt_size))
3866
3867 for g, u in self._group_updates.items():
3868 if u.src_size is not None and u.tgt_size is None:
3869 append('remove_group %s' % g)
3870 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003871 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003872 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3873 append('resize_group %s %d' % (g, u.tgt_size))
3874
3875 for g, u in self._group_updates.items():
3876 if u.src_size is None and u.tgt_size is not None:
3877 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3878 append('add_group %s %d' % (g, u.tgt_size))
3879 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003880 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003881 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3882 append('resize_group %s %d' % (g, u.tgt_size))
3883
3884 for p, u in self._partition_updates.items():
3885 if u.tgt_group and not u.src_group:
3886 comment('Add partition %s to group %s' % (p, u.tgt_group))
3887 append('add %s %s' % (p, u.tgt_group))
3888
3889 for p, u in self._partition_updates.items():
3890 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003891 comment('Grow partition %s from %d to %d' %
3892 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003893 append('resize %s %d' % (p, u.tgt_size))
3894
3895 for p, u in self._partition_updates.items():
3896 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3897 comment('Move partition %s from default to %s' %
3898 (p, u.tgt_group))
3899 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003900
3901
jiajia tangf3f842b2021-03-17 21:49:44 +08003902def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003903 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003904 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003905
3906 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003907 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003908
3909 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003910 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003911 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003912 tmp_dir = MakeTempDir('boot_', suffix='.img')
3913 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003914 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3915 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003916 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3917 if not os.path.isfile(ramdisk):
3918 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3919 return None
3920 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003921 if ramdisk_format == RamdiskFormat.LZ4:
3922 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3923 elif ramdisk_format == RamdiskFormat.GZ:
3924 with open(ramdisk, 'rb') as input_stream:
3925 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003926 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3927 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003928 p2.wait()
3929 else:
3930 logger.error('Only support lz4 or minigzip ramdisk format.')
3931 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003932
3933 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3934 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3935 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3936 # the host environment.
3937 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003938 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003939
Yifan Hongc65a0542021-01-07 14:21:01 -08003940 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3941 prop_file = os.path.join(extracted_ramdisk, search_path)
3942 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003943 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003944 logger.warning(
3945 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003946
Yifan Hong7dc51172021-01-12 11:27:39 -08003947 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003948
Yifan Hong85ac5012021-01-07 14:43:46 -08003949 except ExternalError as e:
3950 logger.warning('Unable to get boot image build props: %s', e)
3951 return None
3952
3953
3954def GetBootImageTimestamp(boot_img):
3955 """
3956 Get timestamp from ramdisk within the boot image
3957
3958 Args:
3959 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3960
3961 Return:
3962 An integer that corresponds to the timestamp of the boot image, or None
3963 if file has unknown format. Raise exception if an unexpected error has
3964 occurred.
3965 """
3966 prop_file = GetBootImageBuildProp(boot_img)
3967 if not prop_file:
3968 return None
3969
3970 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3971 if props is None:
3972 return None
3973
3974 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003975 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3976 if timestamp:
3977 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003978 logger.warning(
3979 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003980 return None
3981
3982 except ExternalError as e:
3983 logger.warning('Unable to get boot image timestamp: %s', e)
3984 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003985
3986
Kelvin Zhang26390482021-11-02 14:31:10 -07003987def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07003988 if not os.path.exists(filepath):
3989 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07003990 with open(filepath, 'rb') as fp:
3991 # Magic for android sparse image format
3992 # https://source.android.com/devices/bootloader/images
3993 return fp.read(4) == b'\x3A\xFF\x26\xED'