blob: 513fce541faae8e249d4365a47d802a9da6c20f1 [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
Abhishek Nigam1dfca462023-11-08 02:21:39 +000018import 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
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Abhishek Nigam1dfca462023-11-08 02:21:39 +000026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
Kelvin Zhange473ce92023-06-21 13:06:59 -070036import stat
Dennis Song6e5e44d2023-10-03 02:18:06 +000037import sys
Doug Zongkereef39442009-04-02 12:14:19 -070038import tempfile
Abhishek Nigam1dfca462023-11-08 02:21:39 +000039import threading
40import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070041import zipfile
Kelvin Zhangc68c6b92023-11-14 10:54:50 -080042
43from typing import Iterable, Callable
Dennis Song4aae62e2023-10-02 04:31:34 +000044from dataclasses import dataclass
Tao Bao12d87fc2018-01-31 12:18:52 -080045from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tianjie Xu41976c72019-07-03 13:57:01 -070047import images
Tao Baoc765cca2018-01-31 17:32:40 -080048import sparse_img
Abhishek Nigam1dfca462023-11-08 02:21:39 +000049from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070050
Tao Bao32fcdab2018-10-12 10:30:39 -070051logger = logging.getLogger(__name__)
52
Tao Bao986ee862018-10-04 15:46:16 -070053
Kelvin Zhangc68c6b92023-11-14 10:54:50 -080054@dataclass
55class OptionHandler:
56 extra_long_opts: Iterable[str]
57 handler: Callable
58
Dan Albert8b72aef2015-03-23 19:13:21 -070059class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070060
Dan Albert8b72aef2015-03-23 19:13:21 -070061 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070062 # Set up search path, in order to find framework/ and lib64/. At the time of
63 # running this function, user-supplied search path (`--path`) hasn't been
64 # available. So the value set here is the default, which might be overridden
65 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040066 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070067 if exec_path.endswith('.py'):
68 script_name = os.path.basename(exec_path)
69 # logger hasn't been initialized yet at this point. Use print to output
70 # warnings.
71 print(
72 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040073 'executable -- build and run `{}` directly.'.format(
74 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070075 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040076 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030077
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080079 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
80 if "ANDROID_HOST_OUT" in os.environ:
81 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080082 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070083 self.extra_signapk_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000084 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.java_path = "java" # Use the one on the path by default.
Sorin Basca05085832022-09-14 11:33:22 +010086 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080087 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070088 self.public_key_suffix = ".x509.pem"
89 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070090 # use otatools built boot_signer by default
Dan Albert8b72aef2015-03-23 19:13:21 -070091 self.verbose = False
92 self.tempfiles = []
93 self.device_specific = None
94 self.extras = {}
95 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070096 self.source_info_dict = None
97 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070098 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070099 # Stash size cannot exceed cache_size * threshold.
100 self.cache_size = None
101 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -0700102 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -0700103
104
105OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700106
Tao Bao71197512018-10-11 14:08:45 -0700107# The block size that's used across the releasetools scripts.
108BLOCK_SIZE = 4096
109
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800110# Values for "certificate" in apkcerts that mean special things.
111SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
112
Tao Bao5cc0abb2019-03-21 10:18:05 -0700113# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
114# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800115# descriptor into vbmeta.img. When adding a new entry here, the
116# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
117# accordingly.
Dennis Song6e5e44d2023-10-03 02:18:06 +0000118AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw',
119 'recovery', 'system', 'system_ext', 'vendor', 'vendor_boot',
120 'vendor_kernel_boot', 'vendor_dlkm', 'odm_dlkm',
121 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800122
Tao Bao08c190f2019-06-03 23:07:58 -0700123# Chained VBMeta partitions.
124AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
125
Dennis Song6e5e44d2023-10-03 02:18:06 +0000126# avbtool arguments name
127AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG = '--include_descriptors_from_image'
128AVB_ARG_NAME_CHAIN_PARTITION = '--chain_partition'
129
Tianjie Xu861f4132018-09-12 11:49:33 -0700130# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400131PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700132 'system',
133 'vendor',
134 'product',
135 'system_ext',
136 'odm',
137 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700138 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000139 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400140]
Tianjie Xu861f4132018-09-12 11:49:33 -0700141
Yifan Hong5057b952021-01-07 14:09:57 -0800142# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000143PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800144
Yifan Hongc65a0542021-01-07 14:21:01 -0800145# See sysprop.mk. If file is moved, add new search paths here; don't remove
146# existing search paths.
147RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700148
Kelvin Zhang563750f2021-04-28 12:46:17 -0400149
Dennis Song4aae62e2023-10-02 04:31:34 +0000150@dataclass
151class AvbChainedPartitionArg:
152 """The required arguments for avbtool --chain_partition."""
153 partition: str
154 rollback_index_location: int
155 pubkey_path: str
156
157 def to_string(self):
158 """Convert to string command arguments."""
159 return '{}:{}:{}'.format(
160 self.partition, self.rollback_index_location, self.pubkey_path)
161
162
Abhishek Nigam1dfca462023-11-08 02:21:39 +0000163class ErrorCode(object):
164 """Define error_codes for failures that happen during the actual
165 update package installation.
166
167 Error codes 0-999 are reserved for failures before the package
168 installation (i.e. low battery, package verification failure).
169 Detailed code in 'bootable/recovery/error_code.h' """
170
171 SYSTEM_VERIFICATION_FAILURE = 1000
172 SYSTEM_UPDATE_FAILURE = 1001
173 SYSTEM_UNEXPECTED_CONTENTS = 1002
174 SYSTEM_NONZERO_CONTENTS = 1003
175 SYSTEM_RECOVER_FAILURE = 1004
176 VENDOR_VERIFICATION_FAILURE = 2000
177 VENDOR_UPDATE_FAILURE = 2001
178 VENDOR_UNEXPECTED_CONTENTS = 2002
179 VENDOR_NONZERO_CONTENTS = 2003
180 VENDOR_RECOVER_FAILURE = 2004
181 OEM_PROP_MISMATCH = 3000
182 FINGERPRINT_MISMATCH = 3001
183 THUMBPRINT_MISMATCH = 3002
184 OLDER_BUILD = 3003
185 DEVICE_MISMATCH = 3004
186 BAD_PATCH_FILE = 3005
187 INSUFFICIENT_CACHE_SPACE = 3006
188 TUNE_PARTITION_FAILURE = 3007
189 APPLY_PATCH_FAILURE = 3008
190
191
Dan Albert8b72aef2015-03-23 19:13:21 -0700192class ExternalError(RuntimeError):
193 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700194
195
Tao Bao32fcdab2018-10-12 10:30:39 -0700196def InitLogging():
197 DEFAULT_LOGGING_CONFIG = {
198 'version': 1,
199 'disable_existing_loggers': False,
200 'formatters': {
201 'standard': {
202 'format':
203 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
204 'datefmt': '%Y-%m-%d %H:%M:%S',
205 },
206 },
207 'handlers': {
208 'default': {
209 'class': 'logging.StreamHandler',
210 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700211 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700212 },
213 },
214 'loggers': {
215 '': {
216 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700217 'propagate': True,
Kelvin Zhang9d741282023-10-24 15:35:54 -0700218 'level': 'NOTSET',
Tao Bao32fcdab2018-10-12 10:30:39 -0700219 }
220 }
221 }
222 env_config = os.getenv('LOGGING_CONFIG')
223 if env_config:
224 with open(env_config) as f:
225 config = json.load(f)
226 else:
227 config = DEFAULT_LOGGING_CONFIG
228
229 # Increase the logging level for verbose mode.
230 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700231 config = copy.deepcopy(config)
232 config['handlers']['default']['level'] = 'INFO'
233
234 if OPTIONS.logfile:
235 config = copy.deepcopy(config)
236 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400237 'class': 'logging.FileHandler',
238 'formatter': 'standard',
239 'level': 'INFO',
240 'mode': 'w',
241 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700242 }
243 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700244
245 logging.config.dictConfig(config)
246
247
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900248def FindHostToolPath(tool_name):
249 """Finds the path to the host tool.
250
251 Args:
252 tool_name: name of the tool to find
253 Returns:
Cole Faust6833d7d2023-08-01 18:00:37 -0700254 path to the tool if found under the same directory as this binary is located at. If not found,
255 tool_name is returned.
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900256 """
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900257 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
258 tool_path = os.path.join(my_dir, tool_name)
259 if os.path.exists(tool_path):
260 return tool_path
261
262 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700263
Kelvin Zhang563750f2021-04-28 12:46:17 -0400264
Tao Bao39451582017-05-04 11:10:47 -0700265def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700266 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 Args:
269 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700270 verbose: Whether the commands should be shown. Default to the global
271 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700272 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
273 stdin, etc. stdout and stderr will default to subprocess.PIPE and
274 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800275 universal_newlines will default to True, as most of the users in
276 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700277
278 Returns:
279 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700280 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700281 if 'stdout' not in kwargs and 'stderr' not in kwargs:
282 kwargs['stdout'] = subprocess.PIPE
283 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800284 if 'universal_newlines' not in kwargs:
285 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700286
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900287 if args:
288 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700289 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900290 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700291
Kelvin Zhang766eea72021-06-03 09:36:08 -0400292 if verbose is None:
293 verbose = OPTIONS.verbose
294
Tao Bao32fcdab2018-10-12 10:30:39 -0700295 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400296 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700297 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700298 return subprocess.Popen(args, **kwargs)
299
300
Tao Bao986ee862018-10-04 15:46:16 -0700301def RunAndCheckOutput(args, verbose=None, **kwargs):
302 """Runs the given command and returns the output.
303
304 Args:
305 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700306 verbose: Whether the commands should be shown. Default to the global
307 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700308 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
309 stdin, etc. stdout and stderr will default to subprocess.PIPE and
310 subprocess.STDOUT respectively unless caller specifies any of them.
311
312 Returns:
313 The output string.
314
315 Raises:
316 ExternalError: On non-zero exit from the command.
317 """
Kelvin Zhangc8ff84b2023-02-15 16:52:46 -0800318 if verbose is None:
319 verbose = OPTIONS.verbose
Tao Bao986ee862018-10-04 15:46:16 -0700320 proc = Run(args, verbose=verbose, **kwargs)
321 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800322 if output is None:
323 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700324 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400325 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700326 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700327 if proc.returncode != 0:
328 raise ExternalError(
329 "Failed to run command '{}' (exit code {}):\n{}".format(
330 args, proc.returncode, output))
331 return output
332
333
Tao Baoc765cca2018-01-31 17:32:40 -0800334def RoundUpTo4K(value):
335 rounded_up = value + 4095
336 return rounded_up - (rounded_up % 4096)
337
338
Ying Wang7e6d4e42010-12-13 16:25:36 -0800339def CloseInheritedPipes():
340 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
341 before doing other work."""
342 if platform.system() != "Darwin":
343 return
344 for d in range(3, 1025):
345 try:
346 stat = os.fstat(d)
347 if stat is not None:
348 pipebit = stat[0] & 0x1000
349 if pipebit != 0:
350 os.close(d)
351 except OSError:
352 pass
353
354
Tao Bao1c320f82019-10-04 23:25:12 -0700355class BuildInfo(object):
356 """A class that holds the information for a given build.
357
358 This class wraps up the property querying for a given source or target build.
359 It abstracts away the logic of handling OEM-specific properties, and caches
360 the commonly used properties such as fingerprint.
361
362 There are two types of info dicts: a) build-time info dict, which is generated
363 at build time (i.e. included in a target_files zip); b) OEM info dict that is
364 specified at package generation time (via command line argument
365 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
366 having "oem_fingerprint_properties" in build-time info dict), all the queries
367 would be answered based on build-time info dict only. Otherwise if using
368 OEM-specific properties, some of them will be calculated from two info dicts.
369
370 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800371 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700372
373 Attributes:
374 info_dict: The build-time info dict.
375 is_ab: Whether it's a build that uses A/B OTA.
376 oem_dicts: A list of OEM dicts.
377 oem_props: A list of OEM properties that should be read from OEM dicts; None
378 if the build doesn't use any OEM-specific property.
379 fingerprint: The fingerprint of the build, which would be calculated based
380 on OEM properties if applicable.
381 device: The device name, which could come from OEM dicts if applicable.
382 """
383
384 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
385 "ro.product.manufacturer", "ro.product.model",
386 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700387 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
388 "product", "odm", "vendor", "system_ext", "system"]
389 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
390 "product", "product_services", "odm", "vendor", "system"]
391 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700392
Tianjiefdda51d2021-05-05 14:46:35 -0700393 # The length of vbmeta digest to append to the fingerprint
394 _VBMETA_DIGEST_SIZE_USED = 8
395
396 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700397 """Initializes a BuildInfo instance with the given dicts.
398
399 Note that it only wraps up the given dicts, without making copies.
400
401 Arguments:
402 info_dict: The build-time info dict.
403 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
404 that it always uses the first dict to calculate the fingerprint or the
405 device name. The rest would be used for asserting OEM properties only
406 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700407 use_legacy_id: Use the legacy build id to construct the fingerprint. This
408 is used when we need a BuildInfo class, while the vbmeta digest is
409 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700410
411 Raises:
412 ValueError: On invalid inputs.
413 """
414 self.info_dict = info_dict
415 self.oem_dicts = oem_dicts
416
417 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700418 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700419
Hongguang Chend7c160f2020-05-03 21:24:26 -0700420 # Skip _oem_props if oem_dicts is None to use BuildInfo in
421 # sign_target_files_apks
422 if self.oem_dicts:
423 self._oem_props = info_dict.get("oem_fingerprint_properties")
424 else:
425 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700426
Daniel Normand5fe8622020-01-08 17:01:11 -0800427 def check_fingerprint(fingerprint):
428 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
429 raise ValueError(
430 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
431 "3.2.2. Build Parameters.".format(fingerprint))
432
Daniel Normand5fe8622020-01-08 17:01:11 -0800433 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800434 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 try:
436 fingerprint = self.CalculatePartitionFingerprint(partition)
437 check_fingerprint(fingerprint)
438 self._partition_fingerprints[partition] = fingerprint
439 except ExternalError:
440 continue
441 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800442 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800443 # need a fingerprint when creating the image.
444 self._partition_fingerprints[
445 "system_other"] = self._partition_fingerprints["system"]
446
Tao Bao1c320f82019-10-04 23:25:12 -0700447 # These two should be computed only after setting self._oem_props.
448 self._device = self.GetOemProperty("ro.product.device")
449 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800450 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700451
452 @property
453 def is_ab(self):
454 return self._is_ab
455
456 @property
457 def device(self):
458 return self._device
459
460 @property
461 def fingerprint(self):
462 return self._fingerprint
463
464 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400465 def is_vabc(self):
Kelvin Zhange634bde2023-04-28 23:59:43 -0700466 return self.info_dict.get("virtual_ab_compression") == "true"
Kelvin Zhang563750f2021-04-28 12:46:17 -0400467
468 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700469 def is_android_r(self):
470 system_prop = self.info_dict.get("system.build.prop")
471 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
472
473 @property
Kelvin Zhang2f9a9ae2023-09-27 09:33:52 -0700474 def is_release_key(self):
475 system_prop = self.info_dict.get("build.prop")
476 return system_prop and system_prop.GetProp("ro.build.tags") == "release-key"
477
478 @property
Kelvin Zhang8f830002023-08-16 13:16:48 -0700479 def vabc_compression_param(self):
480 return self.get("virtual_ab_compression_method", "")
481
482 @property
Daniel Zheng474afa82024-02-28 12:59:52 -0800483 def vabc_cow_version(self):
484 return self.get("virtual_ab_cow_version", "")
485
486 @property
David Anderson1c596172023-04-14 16:01:55 -0700487 def vendor_api_level(self):
488 vendor_prop = self.info_dict.get("vendor.build.prop")
489 if not vendor_prop:
490 return -1
491
492 props = [
493 "ro.board.api_level",
494 "ro.board.first_api_level",
495 "ro.product.first_api_level",
496 ]
497 for prop in props:
498 value = vendor_prop.GetProp(prop)
499 try:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700500 return int(value)
David Anderson1c596172023-04-14 16:01:55 -0700501 except:
Kelvin Zhange634bde2023-04-28 23:59:43 -0700502 pass
David Anderson1c596172023-04-14 16:01:55 -0700503 return -1
504
505 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700506 def is_vabc_xor(self):
507 vendor_prop = self.info_dict.get("vendor.build.prop")
508 vabc_xor_enabled = vendor_prop and \
509 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
510 return vabc_xor_enabled
511
512 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400513 def vendor_suppressed_vabc(self):
514 vendor_prop = self.info_dict.get("vendor.build.prop")
515 vabc_suppressed = vendor_prop and \
516 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
517 return vabc_suppressed and vabc_suppressed.lower() == "true"
518
519 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700520 def oem_props(self):
521 return self._oem_props
522
523 def __getitem__(self, key):
524 return self.info_dict[key]
525
526 def __setitem__(self, key, value):
527 self.info_dict[key] = value
528
529 def get(self, key, default=None):
530 return self.info_dict.get(key, default)
531
532 def items(self):
533 return self.info_dict.items()
534
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000535 def _GetRawBuildProp(self, prop, partition):
536 prop_file = '{}.build.prop'.format(
537 partition) if partition else 'build.prop'
538 partition_props = self.info_dict.get(prop_file)
539 if not partition_props:
540 return None
541 return partition_props.GetProp(prop)
542
Daniel Normand5fe8622020-01-08 17:01:11 -0800543 def GetPartitionBuildProp(self, prop, partition):
544 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800545
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000546 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000547 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000548 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800549
Daniel Normand5fe8622020-01-08 17:01:11 -0800550 # If provided a partition for this property, only look within that
551 # partition's build.prop.
552 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800553 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800554 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800555 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000556
557 prop_val = self._GetRawBuildProp(prop, partition)
558 if prop_val is not None:
559 return prop_val
560 raise ExternalError("couldn't find %s in %s.build.prop" %
561 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800562
Tao Bao1c320f82019-10-04 23:25:12 -0700563 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800564 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700565 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
566 return self._ResolveRoProductBuildProp(prop)
567
Tianjiefdda51d2021-05-05 14:46:35 -0700568 if prop == "ro.build.id":
569 return self._GetBuildId()
570
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000571 prop_val = self._GetRawBuildProp(prop, None)
572 if prop_val is not None:
573 return prop_val
574
575 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700576
577 def _ResolveRoProductBuildProp(self, prop):
578 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000579 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700580 if prop_val:
581 return prop_val
582
Steven Laver8e2086e2020-04-27 16:26:31 -0700583 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000584 source_order_val = self._GetRawBuildProp(
585 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700586 if source_order_val:
587 source_order = source_order_val.split(",")
588 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700589 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700590
591 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700592 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700593 raise ExternalError(
594 "Invalid ro.product.property_source_order '{}'".format(source_order))
595
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000596 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700597 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000598 "ro.product", "ro.product.{}".format(source_partition), 1)
599 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700600 if prop_val:
601 return prop_val
602
603 raise ExternalError("couldn't resolve {}".format(prop))
604
Steven Laver8e2086e2020-04-27 16:26:31 -0700605 def _GetRoProductPropsDefaultSourceOrder(self):
606 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
607 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000608 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700609 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000610 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700611 if android_version == "10":
612 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
613 # NOTE: float() conversion of android_version will have rounding error.
614 # We are checking for "9" or less, and using "< 10" is well outside of
615 # possible floating point rounding.
616 try:
617 android_version_val = float(android_version)
618 except ValueError:
619 android_version_val = 0
620 if android_version_val < 10:
621 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
622 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
623
Tianjieb37c5be2020-10-15 21:27:10 -0700624 def _GetPlatformVersion(self):
625 version_sdk = self.GetBuildProp("ro.build.version.sdk")
626 # init code switches to version_release_or_codename (see b/158483506). After
627 # API finalization, release_or_codename will be the same as release. This
628 # is the best effort to support pre-S dev stage builds.
629 if int(version_sdk) >= 30:
630 try:
631 return self.GetBuildProp("ro.build.version.release_or_codename")
632 except ExternalError:
633 logger.warning('Failed to find ro.build.version.release_or_codename')
634
635 return self.GetBuildProp("ro.build.version.release")
636
Tianjiefdda51d2021-05-05 14:46:35 -0700637 def _GetBuildId(self):
638 build_id = self._GetRawBuildProp("ro.build.id", None)
639 if build_id:
640 return build_id
641
642 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
643 if not legacy_build_id:
644 raise ExternalError("Couldn't find build id in property file")
645
646 if self.use_legacy_id:
647 return legacy_build_id
648
649 # Append the top 8 chars of vbmeta digest to the existing build id. The
650 # logic needs to match the one in init, so that OTA can deliver correctly.
651 avb_enable = self.info_dict.get("avb_enable") == "true"
652 if not avb_enable:
653 raise ExternalError("AVB isn't enabled when using legacy build id")
654
655 vbmeta_digest = self.info_dict.get("vbmeta_digest")
656 if not vbmeta_digest:
657 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
658 " id")
659 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
660 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
661
662 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
663 return legacy_build_id + '.' + digest_prefix
664
Tianjieb37c5be2020-10-15 21:27:10 -0700665 def _GetPartitionPlatformVersion(self, partition):
666 try:
667 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
668 partition)
669 except ExternalError:
670 return self.GetPartitionBuildProp("ro.build.version.release",
671 partition)
672
Tao Bao1c320f82019-10-04 23:25:12 -0700673 def GetOemProperty(self, key):
674 if self.oem_props is not None and key in self.oem_props:
675 return self.oem_dicts[0][key]
676 return self.GetBuildProp(key)
677
Daniel Normand5fe8622020-01-08 17:01:11 -0800678 def GetPartitionFingerprint(self, partition):
679 return self._partition_fingerprints.get(partition, None)
680
681 def CalculatePartitionFingerprint(self, partition):
682 try:
683 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
684 except ExternalError:
685 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
686 self.GetPartitionBuildProp("ro.product.brand", partition),
687 self.GetPartitionBuildProp("ro.product.name", partition),
688 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700689 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800690 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400691 self.GetPartitionBuildProp(
692 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800693 self.GetPartitionBuildProp("ro.build.type", partition),
694 self.GetPartitionBuildProp("ro.build.tags", partition))
695
Tao Bao1c320f82019-10-04 23:25:12 -0700696 def CalculateFingerprint(self):
697 if self.oem_props is None:
698 try:
699 return self.GetBuildProp("ro.build.fingerprint")
700 except ExternalError:
701 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
702 self.GetBuildProp("ro.product.brand"),
703 self.GetBuildProp("ro.product.name"),
704 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700705 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700706 self.GetBuildProp("ro.build.id"),
707 self.GetBuildProp("ro.build.version.incremental"),
708 self.GetBuildProp("ro.build.type"),
709 self.GetBuildProp("ro.build.tags"))
710 return "%s/%s/%s:%s" % (
711 self.GetOemProperty("ro.product.brand"),
712 self.GetOemProperty("ro.product.name"),
713 self.GetOemProperty("ro.product.device"),
714 self.GetBuildProp("ro.build.thumbprint"))
715
716 def WriteMountOemScript(self, script):
717 assert self.oem_props is not None
718 recovery_mount_options = self.info_dict.get("recovery_mount_options")
719 script.Mount("/oem", recovery_mount_options)
720
721 def WriteDeviceAssertions(self, script, oem_no_mount):
722 # Read the property directly if not using OEM properties.
723 if not self.oem_props:
724 script.AssertDevice(self.device)
725 return
726
727 # Otherwise assert OEM properties.
728 if not self.oem_dicts:
729 raise ExternalError(
730 "No OEM file provided to answer expected assertions")
731
732 for prop in self.oem_props.split():
733 values = []
734 for oem_dict in self.oem_dicts:
735 if prop in oem_dict:
736 values.append(oem_dict[prop])
737 if not values:
738 raise ExternalError(
739 "The OEM file is missing the property %s" % (prop,))
740 script.AssertOemProperty(prop, values, oem_no_mount)
741
742
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700743def DoesInputFileContain(input_file, fn):
744 """Check whether the input target_files.zip contain an entry `fn`"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000745 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700746 return fn in input_file.namelist()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700747 elif zipfile.is_zipfile(input_file):
748 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700749 return fn in zfp.namelist()
750 else:
751 if not os.path.isdir(input_file):
752 raise ValueError(
753 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
754 path = os.path.join(input_file, *fn.split("/"))
755 return os.path.exists(path)
756
757
758def ReadBytesFromInputFile(input_file, fn):
759 """Reads the bytes of fn from input zipfile or directory."""
760 if isinstance(input_file, zipfile.ZipFile):
761 return input_file.read(fn)
762 elif zipfile.is_zipfile(input_file):
763 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
764 return zfp.read(fn)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000765 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700766 if not os.path.isdir(input_file):
767 raise ValueError(
768 "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 +0000769 path = os.path.join(input_file, *fn.split("/"))
770 try:
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700771 with open(path, "rb") as f:
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000772 return f.read()
773 except IOError as e:
774 if e.errno == errno.ENOENT:
775 raise KeyError(fn)
776
777
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700778def ReadFromInputFile(input_file, fn):
779 """Reads the str contents of fn from input zipfile or directory."""
780 return ReadBytesFromInputFile(input_file, fn).decode()
781
782
Kelvin Zhang6b10e152023-05-02 15:48:16 -0700783def WriteBytesToInputFile(input_file, fn, data):
784 """Write bytes |data| contents to fn of input zipfile or directory."""
785 if isinstance(input_file, zipfile.ZipFile):
786 with input_file.open(fn, "w") as entry_fp:
787 return entry_fp.write(data)
788 elif zipfile.is_zipfile(input_file):
789 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
790 with zfp.open(fn, "w") as entry_fp:
791 return entry_fp.write(data)
792 else:
793 if not os.path.isdir(input_file):
794 raise ValueError(
795 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
796 path = os.path.join(input_file, *fn.split("/"))
797 try:
798 with open(path, "wb") as f:
799 return f.write(data)
800 except IOError as e:
801 if e.errno == errno.ENOENT:
802 raise KeyError(fn)
803
804
805def WriteToInputFile(input_file, fn, str: str):
806 """Write str content to fn of input file or directory"""
807 return WriteBytesToInputFile(input_file, fn, str.encode())
808
809
Yifan Hong10482a22021-01-07 14:38:41 -0800810def ExtractFromInputFile(input_file, fn):
811 """Extracts the contents of fn from input zipfile or directory into a file."""
812 if isinstance(input_file, zipfile.ZipFile):
813 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500814 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800815 f.write(input_file.read(fn))
816 return tmp_file
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700817 elif zipfile.is_zipfile(input_file):
818 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
819 tmp_file = MakeTempFile(os.path.basename(fn))
820 with open(tmp_file, "wb") as fp:
821 fp.write(zfp.read(fn))
822 return tmp_file
Yifan Hong10482a22021-01-07 14:38:41 -0800823 else:
Kelvin Zhangeb147e02022-10-21 10:53:21 -0700824 if not os.path.isdir(input_file):
825 raise ValueError(
826 "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 -0800827 file = os.path.join(input_file, *fn.split("/"))
828 if not os.path.exists(file):
829 raise KeyError(fn)
830 return file
831
Kelvin Zhang563750f2021-04-28 12:46:17 -0400832
jiajia tangf3f842b2021-03-17 21:49:44 +0800833class RamdiskFormat(object):
834 LZ4 = 1
835 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800836
Kelvin Zhang563750f2021-04-28 12:46:17 -0400837
TJ Rhoades6f488e92022-05-01 22:16:22 -0700838def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800839 if info_dict.get('lz4_ramdisks') == 'true':
840 ramdisk_format = RamdiskFormat.LZ4
841 else:
842 ramdisk_format = RamdiskFormat.GZ
843 return ramdisk_format
844
Kelvin Zhang563750f2021-04-28 12:46:17 -0400845
Tao Bao410ad8b2018-08-24 12:08:38 -0700846def LoadInfoDict(input_file, repacking=False):
847 """Loads the key/value pairs from the given input target_files.
848
Tianjiea85bdf02020-07-29 11:56:19 -0700849 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700850 checks and returns the parsed key/value pairs for to the given build. It's
851 usually called early when working on input target_files files, e.g. when
852 generating OTAs, or signing builds. Note that the function may be called
853 against an old target_files file (i.e. from past dessert releases). So the
854 property parsing needs to be backward compatible.
855
856 In a `META/misc_info.txt`, a few properties are stored as links to the files
857 in the PRODUCT_OUT directory. It works fine with the build system. However,
858 they are no longer available when (re)generating images from target_files zip.
859 When `repacking` is True, redirect these properties to the actual files in the
860 unzipped directory.
861
862 Args:
863 input_file: The input target_files file, which could be an open
864 zipfile.ZipFile instance, or a str for the dir that contains the files
865 unzipped from a target_files file.
866 repacking: Whether it's trying repack an target_files file after loading the
867 info dict (default: False). If so, it will rewrite a few loaded
868 properties (e.g. selinux_fc, root_dir) to point to the actual files in
869 target_files file. When doing repacking, `input_file` must be a dir.
870
871 Returns:
872 A dict that contains the parsed key/value pairs.
873
874 Raises:
875 AssertionError: On invalid input arguments.
876 ValueError: On malformed input values.
877 """
878 if repacking:
879 assert isinstance(input_file, str), \
880 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700881
Doug Zongkerc9253822014-02-04 12:17:58 -0800882 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000883 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800884
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700885 try:
Michael Runge6e836112014-04-15 17:40:21 -0700886 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700887 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700888 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700889
Tao Bao410ad8b2018-08-24 12:08:38 -0700890 if "recovery_api_version" not in d:
891 raise ValueError("Failed to find 'recovery_api_version'")
892 if "fstab_version" not in d:
893 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800894
Tao Bao410ad8b2018-08-24 12:08:38 -0700895 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700896 # "selinux_fc" properties should point to the file_contexts files
897 # (file_contexts.bin) under META/.
898 for key in d:
899 if key.endswith("selinux_fc"):
900 fc_basename = os.path.basename(d[key])
901 fc_config = os.path.join(input_file, "META", fc_basename)
902 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700903
Daniel Norman72c626f2019-05-13 15:58:14 -0700904 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700905
Tom Cherryd14b8952018-08-09 14:26:00 -0700906 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700907 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700908 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700909 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700910
David Anderson0ec64ac2019-12-06 12:21:18 -0800911 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700912 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000913 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800914 key_name = part_name + "_base_fs_file"
915 if key_name not in d:
916 continue
917 basename = os.path.basename(d[key_name])
918 base_fs_file = os.path.join(input_file, "META", basename)
919 if os.path.exists(base_fs_file):
920 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700921 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700922 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800923 "Failed to find %s base fs file: %s", part_name, base_fs_file)
924 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700925
Doug Zongker37974732010-09-16 17:44:38 -0700926 def makeint(key):
927 if key in d:
928 d[key] = int(d[key], 0)
929
930 makeint("recovery_api_version")
931 makeint("blocksize")
932 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700933 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700934 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700935 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700936 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800937 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700938
Steve Muckle903a1ca2020-05-07 17:32:10 -0700939 boot_images = "boot.img"
940 if "boot_images" in d:
941 boot_images = d["boot_images"]
942 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400943 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700944
Tao Bao765668f2019-10-04 22:03:00 -0700945 # Load recovery fstab if applicable.
946 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700947 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800948
Tianjie Xu861f4132018-09-12 11:49:33 -0700949 # Tries to load the build props for all partitions with care_map, including
950 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800951 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800952 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000953 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800954 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700955 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800956
Tao Bao12d87fc2018-01-31 12:18:52 -0800957 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700958 # Set the vbmeta digest if exists
959 try:
960 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
961 except KeyError:
962 pass
963
Kelvin Zhang39aea442020-08-17 11:04:25 -0400964 try:
965 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
966 except KeyError:
967 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700968 return d
969
Tao Baod1de6f32017-03-01 16:38:48 -0800970
Daniel Norman4cc9df62019-07-18 10:11:07 -0700971def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900972 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700973 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900974
Daniel Norman4cc9df62019-07-18 10:11:07 -0700975
976def LoadDictionaryFromFile(file_path):
977 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900978 return LoadDictionaryFromLines(lines)
979
980
Michael Runge6e836112014-04-15 17:40:21 -0700981def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700982 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700983 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700984 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700985 if not line or line.startswith("#"):
986 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700987 if "=" in line:
988 name, value = line.split("=", 1)
989 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700990 return d
991
Tao Baod1de6f32017-03-01 16:38:48 -0800992
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000993class PartitionBuildProps(object):
994 """The class holds the build prop of a particular partition.
995
996 This class loads the build.prop and holds the build properties for a given
997 partition. It also partially recognizes the 'import' statement in the
998 build.prop; and calculates alternative values of some specific build
999 properties during runtime.
1000
1001 Attributes:
1002 input_file: a zipped target-file or an unzipped target-file directory.
1003 partition: name of the partition.
1004 props_allow_override: a list of build properties to search for the
1005 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +00001006 build_props: a dict of build properties for the given partition.
1007 prop_overrides: a set of props that are overridden by import.
1008 placeholder_values: A dict of runtime variables' values to replace the
1009 placeholders in the build.prop file. We expect exactly one value for
1010 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +08001011 ramdisk_format: If name is "boot", the format of ramdisk inside the
1012 boot image. Otherwise, its value is ignored.
Elliott Hughes97ad1202023-06-20 16:41:58 -07001013 Use lz4 to decompress by default. If its value is gzip, use gzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001014 """
Kelvin Zhang0876c412020-06-23 15:06:58 -04001015
Tianjie Xu9afb2212020-05-10 21:48:15 +00001016 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001017 self.input_file = input_file
1018 self.partition = name
1019 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +00001020 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001021 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001022 self.prop_overrides = set()
1023 self.placeholder_values = {}
1024 if placeholder_values:
1025 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001026
1027 @staticmethod
1028 def FromDictionary(name, build_props):
1029 """Constructs an instance from a build prop dictionary."""
1030
1031 props = PartitionBuildProps("unknown", name)
1032 props.build_props = build_props.copy()
1033 return props
1034
1035 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +08001036 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001037 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -08001038
Devin Mooreafdd7c72021-12-13 22:04:08 +00001039 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001040 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +00001041 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001042 else:
1043 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
1044
1045 props = PartitionBuildProps(input_file, name, placeholder_values)
1046 props._LoadBuildProp(data)
1047 return props
1048
1049 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +00001050 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -08001051 """
1052 Read build.prop for boot image from input_file.
1053 Return empty string if not found.
1054 """
Devin Mooreafdd7c72021-12-13 22:04:08 +00001055 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -08001056 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001057 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001058 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +00001059 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -08001060 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +08001061 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -08001062 if prop_file is None:
1063 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -05001064 with open(prop_file, "r") as f:
1065 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -08001066
1067 @staticmethod
1068 def _ReadPartitionPropFile(input_file, name):
1069 """
1070 Read build.prop for name from input_file.
1071 Return empty string if not found.
1072 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001073 data = ''
1074 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
1075 '{}/build.prop'.format(name.upper())]:
1076 try:
1077 data = ReadFromInputFile(input_file, prop_file)
1078 break
1079 except KeyError:
1080 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -08001081 if data == '':
1082 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -08001083 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001084
Yifan Hong125d0b62020-09-24 17:07:03 -07001085 @staticmethod
1086 def FromBuildPropFile(name, build_prop_file):
1087 """Constructs an instance from a build prop file."""
1088
1089 props = PartitionBuildProps("unknown", name)
1090 with open(build_prop_file) as f:
1091 props._LoadBuildProp(f.read())
1092 return props
1093
Tianjie Xu9afb2212020-05-10 21:48:15 +00001094 def _LoadBuildProp(self, data):
1095 for line in data.split('\n'):
1096 line = line.strip()
1097 if not line or line.startswith("#"):
1098 continue
1099 if line.startswith("import"):
1100 overrides = self._ImportParser(line)
1101 duplicates = self.prop_overrides.intersection(overrides.keys())
1102 if duplicates:
1103 raise ValueError('prop {} is overridden multiple times'.format(
1104 ','.join(duplicates)))
1105 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1106 self.build_props.update(overrides)
1107 elif "=" in line:
1108 name, value = line.split("=", 1)
1109 if name in self.prop_overrides:
1110 raise ValueError('prop {} is set again after overridden by import '
1111 'statement'.format(name))
1112 self.build_props[name] = value
1113
1114 def _ImportParser(self, line):
1115 """Parses the build prop in a given import statement."""
1116
1117 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001118 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001119 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001120
1121 if len(tokens) == 3:
1122 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1123 return {}
1124
Tianjie Xu9afb2212020-05-10 21:48:15 +00001125 import_path = tokens[1]
1126 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001127 logger.warn('Unrecognized import path {}'.format(line))
1128 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001129
1130 # We only recognize a subset of import statement that the init process
1131 # supports. And we can loose the restriction based on how the dynamic
1132 # fingerprint is used in practice. The placeholder format should be
1133 # ${placeholder}, and its value should be provided by the caller through
1134 # the placeholder_values.
1135 for prop, value in self.placeholder_values.items():
1136 prop_place_holder = '${{{}}}'.format(prop)
1137 if prop_place_holder in import_path:
1138 import_path = import_path.replace(prop_place_holder, value)
1139 if '$' in import_path:
1140 logger.info('Unresolved place holder in import path %s', import_path)
1141 return {}
1142
1143 import_path = import_path.replace('/{}'.format(self.partition),
1144 self.partition.upper())
1145 logger.info('Parsing build props override from %s', import_path)
1146
1147 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1148 d = LoadDictionaryFromLines(lines)
1149 return {key: val for key, val in d.items()
1150 if key in self.props_allow_override}
1151
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001152 def __getstate__(self):
1153 state = self.__dict__.copy()
1154 # Don't pickle baz
1155 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1156 state["input_file"] = state["input_file"].filename
1157 return state
1158
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001159 def GetProp(self, prop):
1160 return self.build_props.get(prop)
1161
1162
Yi-Yo Chiang18650c72022-10-12 18:29:14 +08001163def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001164 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001165 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001166 self.mount_point = mount_point
1167 self.fs_type = fs_type
1168 self.device = device
1169 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001170 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001171 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001172
1173 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001174 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001175 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001176 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001177 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001178
Tao Baod1de6f32017-03-01 16:38:48 -08001179 assert fstab_version == 2
1180
1181 d = {}
1182 for line in data.split("\n"):
1183 line = line.strip()
1184 if not line or line.startswith("#"):
1185 continue
1186
1187 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1188 pieces = line.split()
1189 if len(pieces) != 5:
1190 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1191
1192 # Ignore entries that are managed by vold.
1193 options = pieces[4]
1194 if "voldmanaged=" in options:
1195 continue
1196
1197 # It's a good line, parse it.
1198 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001199 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001200 options = options.split(",")
1201 for i in options:
1202 if i.startswith("length="):
1203 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001204 elif i == "slotselect":
1205 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001206 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001207 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001208 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001209
Tao Baod1de6f32017-03-01 16:38:48 -08001210 mount_flags = pieces[3]
1211 # Honor the SELinux context if present.
1212 context = None
1213 for i in mount_flags.split(","):
1214 if i.startswith("context="):
1215 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001216
Tao Baod1de6f32017-03-01 16:38:48 -08001217 mount_point = pieces[1]
1218 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001219 device=pieces[0], length=length, context=context,
1220 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001221
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001222 return d
1223
1224
Tao Bao765668f2019-10-04 22:03:00 -07001225def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1226 """Finds the path to recovery fstab and loads its contents."""
1227 # recovery fstab is only meaningful when installing an update via recovery
1228 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001229 if info_dict.get('ab_update') == 'true' and \
1230 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001231 return None
1232
1233 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1234 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1235 # cases, since it may load the info_dict from an old build (e.g. when
1236 # generating incremental OTAs from that build).
Tao Bao765668f2019-10-04 22:03:00 -07001237 if info_dict.get('no_recovery') != 'true':
1238 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
Kelvin Zhang2ab69862023-10-27 10:58:05 -07001239 if not DoesInputFileContain(input_file, recovery_fstab_path):
1240 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
Tao Bao765668f2019-10-04 22:03:00 -07001241 return LoadRecoveryFSTab(
Yi-Yo Chiang18650c72022-10-12 18:29:14 +08001242 read_helper, info_dict['fstab_version'], recovery_fstab_path)
Tao Bao765668f2019-10-04 22:03:00 -07001243
1244 if info_dict.get('recovery_as_boot') == 'true':
1245 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
Kelvin Zhang2ab69862023-10-27 10:58:05 -07001246 if not DoesInputFileContain(input_file, recovery_fstab_path):
1247 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
Tao Bao765668f2019-10-04 22:03:00 -07001248 return LoadRecoveryFSTab(
Yi-Yo Chiang18650c72022-10-12 18:29:14 +08001249 read_helper, info_dict['fstab_version'], recovery_fstab_path)
Tao Bao765668f2019-10-04 22:03:00 -07001250
1251 return None
1252
1253
Doug Zongker37974732010-09-16 17:44:38 -07001254def DumpInfoDict(d):
1255 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001256 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001257
Dan Albert8b72aef2015-03-23 19:13:21 -07001258
Daniel Norman55417142019-11-25 16:04:36 -08001259def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001260 """Merges dynamic partition info variables.
1261
1262 Args:
1263 framework_dict: The dictionary of dynamic partition info variables from the
1264 partial framework target files.
1265 vendor_dict: The dictionary of dynamic partition info variables from the
1266 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001267
1268 Returns:
1269 The merged dynamic partition info dictionary.
1270 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001271
1272 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001273 combined = set(a.split())
1274 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001275 combined = [item.strip() for item in combined if item.strip()]
1276 return " ".join(sorted(combined))
1277
1278 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001279 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001280 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1281
1282 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001283 # For keys-value pairs that are the same, copy to merged dict
1284 for key in vendor_dict.keys():
1285 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1286 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001287
1288 merged_dict["dynamic_partition_list"] = uniq_concat(
1289 framework_dict.get("dynamic_partition_list", ""),
1290 vendor_dict.get("dynamic_partition_list", ""))
1291
1292 # Super block devices are defined by the vendor dict.
1293 if "super_block_devices" in vendor_dict:
1294 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001295 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001296 key = "super_%s_device_size" % block_device
1297 if key not in vendor_dict:
1298 raise ValueError("Vendor dict does not contain required key %s." % key)
1299 merged_dict[key] = vendor_dict[key]
1300
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001301 # Partition groups and group sizes are defined by the vendor dict because
1302 # these values may vary for each board that uses a shared system image.
1303 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001304 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001305 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001306 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001307 if key not in vendor_dict:
1308 raise ValueError("Vendor dict does not contain required key %s." % key)
1309 merged_dict[key] = vendor_dict[key]
1310
1311 # Set the partition group's partition list using a concatenation of the
1312 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001313 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001314 merged_dict[key] = uniq_concat(
1315 framework_dict.get(key, ""), vendor_dict.get(key, ""))
Daniel Zheng4a80c542024-03-21 10:13:16 -07001316 # in the case that vendor is on s build, but is taking a v3 -> v3 vabc ota, we want to fallback to v2
1317 if "vabc_cow_version" not in vendor_dict or "vabc_cow_version" not in framework_dict:
1318 merged_dict["vabc_cow_version"] = '2'
1319 else:
1320 merged_dict["vabc_cow_version"] = min(vendor_dict["vabc_cow_version"], framework_dict["vabc_cow_version"])
Daniel Normanb0c75912020-09-24 14:30:21 -07001321 # Various other flags should be copied from the vendor dict, if defined.
1322 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1323 "super_metadata_device", "super_partition_error_limit",
1324 "super_partition_size"):
1325 if key in vendor_dict.keys():
1326 merged_dict[key] = vendor_dict[key]
1327
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001328 return merged_dict
1329
1330
Daniel Norman21c34f72020-11-11 17:25:50 -08001331def PartitionMapFromTargetFiles(target_files_dir):
1332 """Builds a map from partition -> path within an extracted target files directory."""
1333 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1334 possible_subdirs = {
1335 "system": ["SYSTEM"],
1336 "vendor": ["VENDOR", "SYSTEM/vendor"],
1337 "product": ["PRODUCT", "SYSTEM/product"],
1338 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1339 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1340 "vendor_dlkm": [
1341 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1342 ],
1343 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001344 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001345 }
1346 partition_map = {}
1347 for partition, subdirs in possible_subdirs.items():
1348 for subdir in subdirs:
1349 if os.path.exists(os.path.join(target_files_dir, subdir)):
1350 partition_map[partition] = subdir
1351 break
1352 return partition_map
1353
1354
Daniel Normand3351562020-10-29 12:33:11 -07001355def SharedUidPartitionViolations(uid_dict, partition_groups):
1356 """Checks for APK sharedUserIds that cross partition group boundaries.
1357
1358 This uses a single or merged build's shareduid_violation_modules.json
1359 output file, as generated by find_shareduid_violation.py or
1360 core/tasks/find-shareduid-violation.mk.
1361
1362 An error is defined as a sharedUserId that is found in a set of partitions
1363 that span more than one partition group.
1364
1365 Args:
1366 uid_dict: A dictionary created by using the standard json module to read a
1367 complete shareduid_violation_modules.json file.
1368 partition_groups: A list of groups, where each group is a list of
1369 partitions.
1370
1371 Returns:
1372 A list of error messages.
1373 """
1374 errors = []
1375 for uid, partitions in uid_dict.items():
1376 found_in_groups = [
1377 group for group in partition_groups
1378 if set(partitions.keys()) & set(group)
1379 ]
1380 if len(found_in_groups) > 1:
1381 errors.append(
1382 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1383 % (uid, ",".join(sorted(partitions.keys()))))
1384 return errors
1385
1386
Daniel Norman21c34f72020-11-11 17:25:50 -08001387def RunHostInitVerifier(product_out, partition_map):
1388 """Runs host_init_verifier on the init rc files within partitions.
1389
1390 host_init_verifier searches the etc/init path within each partition.
1391
1392 Args:
1393 product_out: PRODUCT_OUT directory, containing partition directories.
1394 partition_map: A map of partition name -> relative path within product_out.
1395 """
1396 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1397 cmd = ["host_init_verifier"]
1398 for partition, path in partition_map.items():
1399 if partition not in allowed_partitions:
1400 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1401 partition)
1402 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1403 # Add --property-contexts if the file exists on the partition.
1404 property_contexts = "%s_property_contexts" % (
1405 "plat" if partition == "system" else partition)
1406 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1407 property_contexts)
1408 if os.path.exists(property_contexts_path):
1409 cmd.append("--property-contexts=%s" % property_contexts_path)
1410 # Add the passwd file if the file exists on the partition.
1411 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1412 if os.path.exists(passwd_path):
1413 cmd.extend(["-p", passwd_path])
1414 return RunAndCheckOutput(cmd)
1415
1416
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001417def AppendAVBSigningArgs(cmd, partition, avb_salt=None):
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001418 """Append signing arguments for avbtool."""
1419 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
Kelvin Zhange634bde2023-04-28 23:59:43 -07001420 key_path = ResolveAVBSigningPathArgs(
1421 OPTIONS.info_dict.get("avb_" + partition + "_key_path"))
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001422 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1423 if key_path and algorithm:
1424 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001425 if avb_salt is None:
1426 avb_salt = OPTIONS.info_dict.get("avb_salt")
Tao Bao2b6dfd62017-09-27 17:17:43 -07001427 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001428 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001429 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001430
1431
zhangyongpeng70756972023-04-12 15:31:33 +08001432def ResolveAVBSigningPathArgs(split_args):
1433
1434 def ResolveBinaryPath(path):
1435 if os.path.exists(path):
1436 return path
Kelvin Zhang97a5afe2023-06-27 10:30:48 -07001437 if OPTIONS.search_path:
1438 new_path = os.path.join(OPTIONS.search_path, path)
1439 if os.path.exists(new_path):
1440 return new_path
zhangyongpeng70756972023-04-12 15:31:33 +08001441 raise ExternalError(
Kelvin Zhang43df0802023-07-24 13:16:03 -07001442 "Failed to find {}".format(path))
zhangyongpeng70756972023-04-12 15:31:33 +08001443
1444 if not split_args:
1445 return split_args
1446
1447 if isinstance(split_args, list):
1448 for index, arg in enumerate(split_args[:-1]):
1449 if arg == '--signing_helper':
1450 signing_helper_path = split_args[index + 1]
1451 split_args[index + 1] = ResolveBinaryPath(signing_helper_path)
1452 break
1453 elif isinstance(split_args, str):
1454 split_args = ResolveBinaryPath(split_args)
1455
1456 return split_args
1457
1458
Tao Bao765668f2019-10-04 22:03:00 -07001459def GetAvbPartitionArg(partition, image, info_dict=None):
Dennis Song4aae62e2023-10-02 04:31:34 +00001460 """Returns the VBMeta arguments for one partition.
Daniel Norman276f0622019-07-26 14:13:51 -07001461
1462 It sets up the VBMeta argument by including the partition descriptor from the
1463 given 'image', or by configuring the partition as a chained partition.
1464
1465 Args:
1466 partition: The name of the partition (e.g. "system").
1467 image: The path to the partition image.
1468 info_dict: A dict returned by common.LoadInfoDict(). Will use
1469 OPTIONS.info_dict if None has been given.
1470
1471 Returns:
Dennis Song4aae62e2023-10-02 04:31:34 +00001472 A list of VBMeta arguments for one partition.
Daniel Norman276f0622019-07-26 14:13:51 -07001473 """
1474 if info_dict is None:
1475 info_dict = OPTIONS.info_dict
1476
1477 # Check if chain partition is used.
1478 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001479 if not key_path:
Dennis Song6e5e44d2023-10-03 02:18:06 +00001480 return [AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG, image]
cfig1aeef722019-09-20 22:45:06 +08001481
1482 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1483 # into vbmeta.img. The recovery image will be configured on an independent
1484 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1485 # See details at
1486 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001487 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001488 return []
1489
1490 # Otherwise chain the partition into vbmeta.
1491 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
Dennis Song6e5e44d2023-10-03 02:18:06 +00001492 return [AVB_ARG_NAME_CHAIN_PARTITION, chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001493
1494
Dennis Song4aae62e2023-10-02 04:31:34 +00001495def GetAvbPartitionsArg(partitions,
1496 resolve_rollback_index_location_conflict=False,
1497 info_dict=None):
1498 """Returns the VBMeta arguments for all AVB partitions.
1499
1500 It sets up the VBMeta argument by calling GetAvbPartitionArg of all
1501 partitions.
1502
1503 Args:
1504 partitions: A dict of all AVB partitions.
1505 resolve_rollback_index_location_conflict: If true, resolve conflicting avb
1506 rollback index locations by assigning the smallest unused value.
1507 info_dict: A dict returned by common.LoadInfoDict().
1508
1509 Returns:
1510 A list of VBMeta arguments for all partitions.
1511 """
1512 # An AVB partition will be linked into a vbmeta partition by either
1513 # AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG or AVB_ARG_NAME_CHAIN_PARTITION, there
1514 # should be no other cases.
1515 valid_args = {
1516 AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG: [],
1517 AVB_ARG_NAME_CHAIN_PARTITION: []
1518 }
1519
1520 for partition, path in partitions.items():
1521 avb_partition_arg = GetAvbPartitionArg(partition, path, info_dict)
1522 if not avb_partition_arg:
1523 continue
1524 arg_name, arg_value = avb_partition_arg
1525 assert arg_name in valid_args
1526 valid_args[arg_name].append(arg_value)
1527
1528 # Copy the arguments for non-chained AVB partitions directly without
1529 # intervention.
1530 avb_args = []
1531 for image in valid_args[AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG]:
1532 avb_args.extend([AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG, image])
1533
1534 # Handle chained AVB partitions. The rollback index location might be
1535 # adjusted if two partitions use the same value. This may happen when mixing
1536 # a shared system image with other vendor images.
1537 used_index_loc = set()
1538 for chained_partition_arg in valid_args[AVB_ARG_NAME_CHAIN_PARTITION]:
1539 if resolve_rollback_index_location_conflict:
1540 while chained_partition_arg.rollback_index_location in used_index_loc:
1541 chained_partition_arg.rollback_index_location += 1
1542
1543 used_index_loc.add(chained_partition_arg.rollback_index_location)
1544 avb_args.extend([AVB_ARG_NAME_CHAIN_PARTITION,
1545 chained_partition_arg.to_string()])
1546
1547 return avb_args
1548
1549
Tao Bao02a08592018-07-22 12:40:45 -07001550def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1551 """Constructs and returns the arg to build or verify a chained partition.
1552
1553 Args:
1554 partition: The partition name.
1555 info_dict: The info dict to look up the key info and rollback index
1556 location.
1557 key: The key to be used for building or verifying the partition. Defaults to
1558 the key listed in info_dict.
1559
1560 Returns:
Dennis Song4aae62e2023-10-02 04:31:34 +00001561 An AvbChainedPartitionArg object with rollback_index_location and
1562 pubkey_path that can be used to build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001563 """
1564 if key is None:
1565 key = info_dict["avb_" + partition + "_key_path"]
zhangyongpeng70756972023-04-12 15:31:33 +08001566 key = ResolveAVBSigningPathArgs(key)
Tao Bao1ac886e2019-06-26 11:58:22 -07001567 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001568 rollback_index_location = info_dict[
1569 "avb_" + partition + "_rollback_index_location"]
Dennis Song4aae62e2023-10-02 04:31:34 +00001570 return AvbChainedPartitionArg(
1571 partition=partition,
1572 rollback_index_location=int(rollback_index_location),
1573 pubkey_path=pubkey_path)
Tao Bao02a08592018-07-22 12:40:45 -07001574
1575
Dennis Song4aae62e2023-10-02 04:31:34 +00001576def BuildVBMeta(image_path, partitions, name, needed_partitions,
1577 resolve_rollback_index_location_conflict=False):
Daniel Norman276f0622019-07-26 14:13:51 -07001578 """Creates a VBMeta image.
1579
1580 It generates the requested VBMeta image. The requested image could be for
1581 top-level or chained VBMeta image, which is determined based on the name.
1582
1583 Args:
1584 image_path: The output path for the new VBMeta image.
1585 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001586 values. Only valid partition names are accepted, as partitions listed
1587 in common.AVB_PARTITIONS and custom partitions listed in
1588 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001589 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1590 needed_partitions: Partitions whose descriptors should be included into the
1591 generated VBMeta image.
Dennis Song4aae62e2023-10-02 04:31:34 +00001592 resolve_rollback_index_location_conflict: If true, resolve conflicting avb
1593 rollback index locations by assigning the smallest unused value.
Daniel Norman276f0622019-07-26 14:13:51 -07001594
1595 Raises:
1596 AssertionError: On invalid input args.
1597 """
1598 avbtool = OPTIONS.info_dict["avb_avbtool"]
1599 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1600 AppendAVBSigningArgs(cmd, name)
1601
Hongguang Chenf23364d2020-04-27 18:36:36 -07001602 custom_partitions = OPTIONS.info_dict.get(
1603 "avb_custom_images_partition_list", "").strip().split()
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001604 custom_avb_partitions = ["vbmeta_" + part for part in OPTIONS.info_dict.get(
1605 "avb_custom_vbmeta_images_partition_list", "").strip().split()]
Hongguang Chenf23364d2020-04-27 18:36:36 -07001606
Dennis Song4aae62e2023-10-02 04:31:34 +00001607 avb_partitions = {}
Daniel Norman276f0622019-07-26 14:13:51 -07001608 for partition, path in partitions.items():
1609 if partition not in needed_partitions:
1610 continue
1611 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001612 partition in AVB_VBMETA_PARTITIONS or
Kelvin Zhangb81b4e32023-01-10 10:37:56 -08001613 partition in custom_avb_partitions or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001614 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001615 'Unknown partition: {}'.format(partition)
1616 assert os.path.exists(path), \
1617 'Failed to find {} for {}'.format(path, partition)
Dennis Song4aae62e2023-10-02 04:31:34 +00001618 avb_partitions[partition] = path
1619 cmd.extend(GetAvbPartitionsArg(avb_partitions,
1620 resolve_rollback_index_location_conflict))
Daniel Norman276f0622019-07-26 14:13:51 -07001621
1622 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1623 if args and args.strip():
1624 split_args = shlex.split(args)
1625 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001626 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001627 # as a path relative to source tree, which may not be available at the
1628 # same location when running this script (we have the input target_files
1629 # zip only). For such cases, we additionally scan other locations (e.g.
1630 # IMAGES/, RADIO/, etc) before bailing out.
Dennis Song6e5e44d2023-10-03 02:18:06 +00001631 if arg == AVB_ARG_NAME_INCLUDE_DESC_FROM_IMG:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001632 chained_image = split_args[index + 1]
1633 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001634 continue
1635 found = False
1636 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1637 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001638 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001639 if os.path.exists(alt_path):
1640 split_args[index + 1] = alt_path
1641 found = True
1642 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001643 assert found, 'Failed to find {}'.format(chained_image)
zhangyongpeng70756972023-04-12 15:31:33 +08001644
1645 split_args = ResolveAVBSigningPathArgs(split_args)
Daniel Norman276f0622019-07-26 14:13:51 -07001646 cmd.extend(split_args)
1647
1648 RunAndCheckOutput(cmd)
1649
1650
jiajia tang836f76b2021-04-02 14:48:26 +08001651def _MakeRamdisk(sourcedir, fs_config_file=None,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001652 dev_node_file=None,
jiajia tang836f76b2021-04-02 14:48:26 +08001653 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001654 ramdisk_img = tempfile.NamedTemporaryFile()
1655
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001656 cmd = ["mkbootfs"]
1657
1658 if fs_config_file and os.access(fs_config_file, os.F_OK):
1659 cmd.extend(["-f", fs_config_file])
1660
1661 if dev_node_file and os.access(dev_node_file, os.F_OK):
1662 cmd.extend(["-n", dev_node_file])
1663
1664 cmd.append(os.path.join(sourcedir, "RAMDISK"))
1665
Steve Mucklee1b10862019-07-10 10:49:37 -07001666 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001667 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001668 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001669 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001670 elif ramdisk_format == RamdiskFormat.GZ:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001671 p2 = Run(["gzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001672 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07001673 raise ValueError("Only support lz4 or gzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001674
1675 p2.wait()
1676 p1.wait()
1677 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001678 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001679
1680 return ramdisk_img
1681
1682
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001683def _BuildBootableImage(image_name, sourcedir, fs_config_file,
1684 dev_node_file=None, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001685 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001686 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001687
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001688 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001689 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1690 we are building a two-step special image (i.e. building a recovery image to
1691 be loaded into /boot in two-step OTAs).
1692
1693 Return the image data, or None if sourcedir does not appear to contains files
1694 for building the requested image.
1695 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001696
Yifan Hong63c5ca12020-10-08 11:54:02 -07001697 if info_dict is None:
1698 info_dict = OPTIONS.info_dict
1699
Steve Muckle9793cf62020-04-08 18:27:00 -07001700 # "boot" or "recovery", without extension.
1701 partition_name = os.path.basename(sourcedir).lower()
1702
Yifan Hong63c5ca12020-10-08 11:54:02 -07001703 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001704 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001705 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1706 logger.info("Excluded kernel binary from recovery image.")
1707 else:
1708 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001709 elif partition_name == "init_boot":
1710 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001711 else:
1712 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001713 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001714 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001715 return None
1716
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001717 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1718
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001719 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001720 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001721
Doug Zongkereef39442009-04-02 12:14:19 -07001722 img = tempfile.NamedTemporaryFile()
1723
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001724 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001725 ramdisk_format = GetRamdiskFormat(info_dict)
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001726 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, dev_node_file,
jiajia tang836f76b2021-04-02 14:48:26 +08001727 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001728
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001729 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1730 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1731
Yifan Hong63c5ca12020-10-08 11:54:02 -07001732 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001733 if kernel_path is not None:
1734 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001735
Benoit Fradina45a8682014-07-14 21:00:43 +02001736 fn = os.path.join(sourcedir, "second")
1737 if os.access(fn, os.F_OK):
1738 cmd.append("--second")
1739 cmd.append(fn)
1740
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001741 fn = os.path.join(sourcedir, "dtb")
1742 if os.access(fn, os.F_OK):
1743 cmd.append("--dtb")
1744 cmd.append(fn)
1745
Doug Zongker171f1cd2009-06-15 22:36:37 -07001746 fn = os.path.join(sourcedir, "cmdline")
1747 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001748 cmd.append("--cmdline")
1749 cmd.append(open(fn).read().rstrip("\n"))
1750
1751 fn = os.path.join(sourcedir, "base")
1752 if os.access(fn, os.F_OK):
1753 cmd.append("--base")
1754 cmd.append(open(fn).read().rstrip("\n"))
1755
Ying Wang4de6b5b2010-08-25 14:29:34 -07001756 fn = os.path.join(sourcedir, "pagesize")
1757 if os.access(fn, os.F_OK):
1758 cmd.append("--pagesize")
1759 cmd.append(open(fn).read().rstrip("\n"))
1760
Steve Mucklef84668e2020-03-16 19:13:46 -07001761 if partition_name == "recovery":
1762 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301763 if not args:
1764 # Fall back to "mkbootimg_args" for recovery image
1765 # in case "recovery_mkbootimg_args" is not set.
1766 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001767 elif partition_name == "init_boot":
1768 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001769 else:
1770 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001771 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001772 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001773
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001774 args = info_dict.get("mkbootimg_version_args")
1775 if args and args.strip():
1776 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001777
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001778 if has_ramdisk:
1779 cmd.extend(["--ramdisk", ramdisk_img.name])
1780
Tao Baod95e9fd2015-03-29 23:07:41 -07001781 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001782 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001783 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001784 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001785 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001786 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001787
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001788 if partition_name == "recovery":
1789 if info_dict.get("include_recovery_dtbo") == "true":
1790 fn = os.path.join(sourcedir, "recovery_dtbo")
1791 cmd.extend(["--recovery_dtbo", fn])
1792 if info_dict.get("include_recovery_acpio") == "true":
1793 fn = os.path.join(sourcedir, "recovery_acpio")
1794 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001795
Tao Bao986ee862018-10-04 15:46:16 -07001796 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001797
Tao Baod95e9fd2015-03-29 23:07:41 -07001798 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001799 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001800 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001801 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001802 # We have switched from the prebuilt futility binary to using the tool
1803 # (futility-host) built from the source. Override the setting in the old
1804 # TF.zip.
1805 futility = info_dict["futility"]
1806 if futility.startswith("prebuilts/"):
1807 futility = "futility-host"
1808 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001809 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001810 info_dict["vboot_key"] + ".vbprivk",
1811 info_dict["vboot_subkey"] + ".vbprivk",
1812 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001813 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001814 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001815
Tao Baof3282b42015-04-01 11:21:55 -07001816 # Clean up the temp files.
1817 img_unsigned.close()
1818 img_keyblock.close()
1819
David Zeuthen8fecb282017-12-01 16:24:01 -05001820 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001821 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001822 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001823 if partition_name == "recovery":
1824 part_size = info_dict["recovery_size"]
1825 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001826 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001827 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001828 "--partition_size", str(part_size), "--partition_name",
1829 partition_name]
Kelvin Zhangde53f7d2023-10-03 12:21:28 -07001830 salt = None
1831 if kernel_path is not None:
1832 with open(kernel_path, "rb") as fp:
1833 salt = sha256(fp.read()).hexdigest()
1834 AppendAVBSigningArgs(cmd, partition_name, salt)
David Zeuthen8fecb282017-12-01 16:24:01 -05001835 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001836 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001837 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1838 cmd.extend(split_args)
Tao Bao986ee862018-10-04 15:46:16 -07001839 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001840
1841 img.seek(os.SEEK_SET, 0)
1842 data = img.read()
1843
1844 if has_ramdisk:
1845 ramdisk_img.close()
1846 img.close()
1847
1848 return data
1849
1850
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001851def _SignBootableImage(image_path, prebuilt_name, partition_name,
1852 info_dict=None):
1853 """Performs AVB signing for a prebuilt boot.img.
1854
1855 Args:
1856 image_path: The full path of the image, e.g., /path/to/boot.img.
1857 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001858 boot-5.10.img, recovery.img or init_boot.img.
1859 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001860 info_dict: The information dict read from misc_info.txt.
1861 """
1862 if info_dict is None:
1863 info_dict = OPTIONS.info_dict
1864
1865 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1866 if info_dict.get("avb_enable") == "true":
1867 avbtool = info_dict["avb_avbtool"]
1868 if partition_name == "recovery":
1869 part_size = info_dict["recovery_size"]
1870 else:
1871 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1872
1873 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1874 "--partition_size", str(part_size), "--partition_name",
1875 partition_name]
Kelvin Zhang160762a2023-10-17 12:27:56 -07001876 # Use sha256 of the kernel as salt for reproducible builds
1877 with tempfile.TemporaryDirectory() as tmpdir:
1878 RunAndCheckOutput(["unpack_bootimg", "--boot_img", image_path, "--out", tmpdir])
1879 for filename in ["kernel", "ramdisk", "vendor_ramdisk00"]:
1880 path = os.path.join(tmpdir, filename)
1881 if os.path.exists(path) and os.path.getsize(path):
Kelvin Zhang9f9ac4e2023-11-01 10:12:03 -07001882 print("Using {} as salt for avb footer of {}".format(
1883 filename, partition_name))
Kelvin Zhang160762a2023-10-17 12:27:56 -07001884 with open(path, "rb") as fp:
1885 salt = sha256(fp.read()).hexdigest()
Kelvin Zhang9f9ac4e2023-11-01 10:12:03 -07001886 break
Kelvin Zhang160762a2023-10-17 12:27:56 -07001887 AppendAVBSigningArgs(cmd, partition_name, salt)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001888 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1889 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08001890 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
1891 cmd.extend(split_args)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001892 RunAndCheckOutput(cmd)
1893
1894
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001895def HasRamdisk(partition_name, info_dict=None):
1896 """Returns true/false to see if a bootable image should have a ramdisk.
1897
1898 Args:
1899 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1900 info_dict: The information dict read from misc_info.txt.
1901 """
1902 if info_dict is None:
1903 info_dict = OPTIONS.info_dict
1904
1905 if partition_name != "boot":
1906 return True # init_boot.img or recovery.img has a ramdisk.
1907
1908 if info_dict.get("recovery_as_boot") == "true":
1909 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1910
Yi-Yo Chiang92a517d2023-12-01 07:02:17 +00001911 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1912 return False # A GKI boot.img has no ramdisk since Android-13.
1913
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001914 if info_dict.get("init_boot") == "true":
1915 # The ramdisk is moved to the init_boot.img, so there is NO
1916 # ramdisk in the boot.img or boot-<kernel version>.img.
1917 return False
1918
1919 return True
1920
1921
Doug Zongkerd5131602012-08-02 14:46:42 -07001922def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001923 info_dict=None, two_step_image=False,
1924 dev_nodes=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001925 """Return a File object with the desired bootable image.
1926
1927 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1928 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1929 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001930
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001931 if info_dict is None:
1932 info_dict = OPTIONS.info_dict
1933
Doug Zongker55d93282011-01-25 17:03:34 -08001934 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1935 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001936 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001937 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001938
1939 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1940 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001941 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001942 return File.FromLocalFile(name, prebuilt_path)
1943
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001944 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001945 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1946 if os.path.exists(prebuilt_path):
1947 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1948 signed_img = MakeTempFile()
1949 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001950 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1951 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001952
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001953 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001954
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001955 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001956
Doug Zongker6f1d0312014-08-22 08:07:12 -07001957 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001958 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001959 os.path.join(unpack_dir, fs_config),
Vincent Donnefort6e861e92023-02-17 10:12:57 +00001960 os.path.join(unpack_dir, 'META/ramdisk_node_list')
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001961 if dev_nodes else None,
Tao Baod42e97e2016-11-30 12:11:57 -08001962 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001963 if data:
1964 return File(name, data)
1965 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001966
Doug Zongkereef39442009-04-02 12:14:19 -07001967
Lucas Wei03230252022-04-18 16:00:40 +08001968def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001969 """Build a vendor boot image from the specified sourcedir.
1970
1971 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1972 turn them into a vendor boot image.
1973
1974 Return the image data, or None if sourcedir does not appear to contains files
1975 for building the requested image.
1976 """
1977
1978 if info_dict is None:
1979 info_dict = OPTIONS.info_dict
1980
1981 img = tempfile.NamedTemporaryFile()
1982
TJ Rhoades6f488e92022-05-01 22:16:22 -07001983 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001984 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001985
1986 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1987 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1988
1989 cmd = [mkbootimg]
1990
1991 fn = os.path.join(sourcedir, "dtb")
1992 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001993 has_vendor_kernel_boot = (info_dict.get(
1994 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001995
1996 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1997 # Otherwise pack dtb into vendor_boot.
1998 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1999 cmd.append("--dtb")
2000 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07002001
2002 fn = os.path.join(sourcedir, "vendor_cmdline")
2003 if os.access(fn, os.F_OK):
2004 cmd.append("--vendor_cmdline")
2005 cmd.append(open(fn).read().rstrip("\n"))
2006
2007 fn = os.path.join(sourcedir, "base")
2008 if os.access(fn, os.F_OK):
2009 cmd.append("--base")
2010 cmd.append(open(fn).read().rstrip("\n"))
2011
2012 fn = os.path.join(sourcedir, "pagesize")
2013 if os.access(fn, os.F_OK):
2014 cmd.append("--pagesize")
2015 cmd.append(open(fn).read().rstrip("\n"))
2016
2017 args = info_dict.get("mkbootimg_args")
2018 if args and args.strip():
2019 cmd.extend(shlex.split(args))
2020
2021 args = info_dict.get("mkbootimg_version_args")
2022 if args and args.strip():
2023 cmd.extend(shlex.split(args))
2024
2025 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
2026 cmd.extend(["--vendor_boot", img.name])
2027
Devin Moore50509012021-01-13 10:45:04 -08002028 fn = os.path.join(sourcedir, "vendor_bootconfig")
2029 if os.access(fn, os.F_OK):
2030 cmd.append("--vendor_bootconfig")
2031 cmd.append(fn)
2032
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002033 ramdisk_fragment_imgs = []
2034 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
2035 if os.access(fn, os.F_OK):
2036 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
2037 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002038 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2039 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002040 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04002041 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
2042 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002043 # Use prebuilt image if found, else create ramdisk from supplied files.
2044 if os.access(fn, os.F_OK):
2045 ramdisk_fragment_pathname = fn
2046 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04002047 ramdisk_fragment_root = os.path.join(
2048 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08002049 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
2050 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002051 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
2052 ramdisk_fragment_pathname = ramdisk_fragment_img.name
2053 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
2054
Steve Mucklee1b10862019-07-10 10:49:37 -07002055 RunAndCheckOutput(cmd)
2056
2057 # AVB: if enabled, calculate and add hash.
2058 if info_dict.get("avb_enable") == "true":
2059 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08002060 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07002061 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08002062 "--partition_size", str(part_size), "--partition_name", partition_name]
2063 AppendAVBSigningArgs(cmd, partition_name)
2064 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07002065 if args and args.strip():
zhangyongpeng70756972023-04-12 15:31:33 +08002066 split_args = ResolveAVBSigningPathArgs(shlex.split(args))
2067 cmd.extend(split_args)
Steve Mucklee1b10862019-07-10 10:49:37 -07002068 RunAndCheckOutput(cmd)
2069
2070 img.seek(os.SEEK_SET, 0)
2071 data = img.read()
2072
Yo Chiangd21e7dc2020-12-10 18:42:47 +08002073 for f in ramdisk_fragment_imgs:
2074 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07002075 ramdisk_img.close()
2076 img.close()
2077
2078 return data
2079
2080
2081def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
2082 info_dict=None):
2083 """Return a File object with the desired vendor boot image.
2084
2085 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2086 the source files in 'unpack_dir'/'tree_subdir'."""
2087
2088 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2089 if os.path.exists(prebuilt_path):
2090 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2091 return File.FromLocalFile(name, prebuilt_path)
2092
2093 logger.info("building image from target_files %s...", tree_subdir)
2094
2095 if info_dict is None:
2096 info_dict = OPTIONS.info_dict
2097
Kelvin Zhang0876c412020-06-23 15:06:58 -04002098 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08002099 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
2100 if data:
2101 return File(name, data)
2102 return None
2103
2104
2105def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002106 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08002107 """Return a File object with the desired vendor kernel boot image.
2108
2109 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2110 the source files in 'unpack_dir'/'tree_subdir'."""
2111
2112 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2113 if os.path.exists(prebuilt_path):
2114 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2115 return File.FromLocalFile(name, prebuilt_path)
2116
2117 logger.info("building image from target_files %s...", tree_subdir)
2118
2119 if info_dict is None:
2120 info_dict = OPTIONS.info_dict
2121
2122 data = _BuildVendorBootImage(
2123 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002124 if data:
2125 return File(name, data)
2126 return None
2127
2128
Narayan Kamatha07bf042017-08-14 14:49:21 +01002129def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002130 """Gunzips the given gzip compressed file to a given output file."""
2131 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002132 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002133 shutil.copyfileobj(in_file, out_file)
2134
2135
Kelvin Zhange473ce92023-06-21 13:06:59 -07002136def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
2137 # According to https://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip/6297838#6297838
2138 # higher bits of |external_attr| are unix file permission and types
2139 unix_filetype = info.external_attr >> 16
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002140 file_perm = unix_filetype & 0o777
Kelvin Zhange473ce92023-06-21 13:06:59 -07002141
2142 def CheckMask(a, mask):
2143 return (a & mask) == mask
2144
2145 def IsSymlink(a):
2146 return CheckMask(a, stat.S_IFLNK)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002147
2148 def IsDir(a):
2149 return CheckMask(a, stat.S_IFDIR)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002150 # python3.11 zipfile implementation doesn't handle symlink correctly
2151 if not IsSymlink(unix_filetype):
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002152 target = input_zip.extract(info, dirname)
2153 # We want to ensure that the file is at least read/writable by owner and readable by all users
2154 if IsDir(unix_filetype):
2155 os.chmod(target, file_perm | 0o755)
2156 else:
2157 os.chmod(target, file_perm | 0o644)
2158 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002159 if dirname is None:
2160 dirname = os.getcwd()
2161 target = os.path.join(dirname, info.filename)
2162 os.makedirs(os.path.dirname(target), exist_ok=True)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002163 if os.path.exists(target):
2164 os.unlink(target)
Kelvin Zhange473ce92023-06-21 13:06:59 -07002165 os.symlink(input_zip.read(info).decode(), target)
Kelvin Zhang4cb28f62023-07-10 12:30:53 -07002166 return target
Kelvin Zhange473ce92023-06-21 13:06:59 -07002167
2168
Tao Bao0ff15de2019-03-20 11:26:06 -07002169def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002170 """Unzips the archive to the given directory.
2171
2172 Args:
2173 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002174 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002175 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2176 archvie. Non-matching patterns will be filtered out. If there's no match
2177 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002178 """
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002179 with zipfile.ZipFile(filename, allowZip64=True, mode="r") as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002180 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002181 entries = input_zip.infolist()
2182 # b/283033491
2183 # Per https://en.wikipedia.org/wiki/ZIP_(file_format)#Central_directory_file_header
2184 # In zip64 mode, central directory record's header_offset field might be
2185 # set to 0xFFFFFFFF if header offset is > 2^32. In this case, the extra
2186 # fields will contain an 8 byte little endian integer at offset 20
2187 # to indicate the actual local header offset.
2188 # As of python3.11, python does not handle zip64 central directories
2189 # correctly, so we will manually do the parsing here.
Kelvin Zhang1e774242023-06-17 09:18:15 -07002190
2191 # ZIP64 central directory extra field has two required fields:
2192 # 2 bytes header ID and 2 bytes size field. Thes two require fields have
2193 # a total size of 4 bytes. Then it has three other 8 bytes field, followed
2194 # by a 4 byte disk number field. The last disk number field is not required
2195 # to be present, but if it is present, the total size of extra field will be
2196 # divisible by 8(because 2+2+4+8*n is always going to be multiple of 8)
2197 # Most extra fields are optional, but when they appear, their must appear
2198 # in the order defined by zip64 spec. Since file header offset is the 2nd
2199 # to last field in zip64 spec, it will only be at last 8 bytes or last 12-4
2200 # bytes, depending on whether disk number is present.
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002201 for entry in entries:
Kelvin Zhang1e774242023-06-17 09:18:15 -07002202 if entry.header_offset == 0xFFFFFFFF:
2203 if len(entry.extra) % 8 == 0:
2204 entry.header_offset = int.from_bytes(entry.extra[-12:-4], "little")
2205 else:
2206 entry.header_offset = int.from_bytes(entry.extra[-8:], "little")
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002207 if patterns is not None:
Kelvin Zhang38d0c372023-06-14 12:53:29 -07002208 filtered = [info for info in entries if any(
2209 [fnmatch.fnmatch(info.filename, p) for p in patterns])]
Tao Bao0ff15de2019-03-20 11:26:06 -07002210
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002211 # There isn't any matching files. Don't unzip anything.
2212 if not filtered:
2213 return
Kelvin Zhange473ce92023-06-21 13:06:59 -07002214 for info in filtered:
2215 UnzipSingleFile(input_zip, info, dirname)
Kelvin Zhang7c9205b2023-06-05 09:58:16 -07002216 else:
Kelvin Zhange473ce92023-06-21 13:06:59 -07002217 for info in entries:
2218 UnzipSingleFile(input_zip, info, dirname)
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002219
2220
Daniel Norman78554ea2021-09-14 10:29:38 -07002221def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002222 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002223
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002224 Args:
2225 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2226 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2227
Daniel Norman78554ea2021-09-14 10:29:38 -07002228 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002229 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002230
Tao Bao1c830bf2017-12-25 10:43:47 -08002231 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002232 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002233 """
Doug Zongkereef39442009-04-02 12:14:19 -07002234
Tao Bao1c830bf2017-12-25 10:43:47 -08002235 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002236 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2237 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002238 UnzipToDir(m.group(1), tmp, patterns)
2239 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002240 filename = m.group(1)
2241 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002242 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002243
Tao Baodba59ee2018-01-09 13:21:02 -08002244 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002245
2246
Yifan Hong8a66a712019-04-04 15:37:57 -07002247def GetUserImage(which, tmpdir, input_zip,
2248 info_dict=None,
2249 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002250 reset_file_map=False):
2251 """Returns an Image object suitable for passing to BlockImageDiff.
2252
2253 This function loads the specified image from the given path. If the specified
2254 image is sparse, it also performs additional processing for OTA purpose. For
2255 example, it always adds block 0 to clobbered blocks list. It also detects
2256 files that cannot be reconstructed from the block list, for whom we should
2257 avoid applying imgdiff.
2258
2259 Args:
2260 which: The partition name.
2261 tmpdir: The directory that contains the prebuilt image and block map file.
2262 input_zip: The target-files ZIP archive.
2263 info_dict: The dict to be looked up for relevant info.
2264 allow_shared_blocks: If image is sparse, whether having shared blocks is
2265 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002266 reset_file_map: If true and image is sparse, reset file map before returning
2267 the image.
2268 Returns:
2269 A Image object. If it is a sparse image and reset_file_map is False, the
2270 image will have file_map info loaded.
2271 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002272 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002273 info_dict = LoadInfoDict(input_zip)
2274
Kelvin Zhang04521282023-03-02 09:42:52 -08002275 is_sparse = IsSparseImage(os.path.join(tmpdir, "IMAGES", which + ".img"))
Yifan Hong8a66a712019-04-04 15:37:57 -07002276
2277 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2278 # shared blocks (i.e. some blocks will show up in multiple files' block
2279 # list). We can only allocate such shared blocks to the first "owner", and
2280 # disable imgdiff for all later occurrences.
2281 if allow_shared_blocks is None:
2282 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2283
2284 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002285 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002286 if reset_file_map:
2287 img.ResetFileMap()
2288 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002289 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002290
2291
hungweichencc9c05d2022-08-23 05:45:42 +00002292def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002293 """Returns a Image object suitable for passing to BlockImageDiff.
2294
2295 This function loads the specified non-sparse image from the given path.
2296
2297 Args:
2298 which: The partition name.
2299 tmpdir: The directory that contains the prebuilt image and block map file.
2300 Returns:
2301 A Image object.
2302 """
2303 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2304 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2305
2306 # The image and map files must have been created prior to calling
2307 # ota_from_target_files.py (since LMP).
2308 assert os.path.exists(path) and os.path.exists(mappath)
2309
hungweichencc9c05d2022-08-23 05:45:42 +00002310 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002311
Yifan Hong8a66a712019-04-04 15:37:57 -07002312
hungweichencc9c05d2022-08-23 05:45:42 +00002313def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002314 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2315
2316 This function loads the specified sparse image from the given path, and
2317 performs additional processing for OTA purpose. For example, it always adds
2318 block 0 to clobbered blocks list. It also detects files that cannot be
2319 reconstructed from the block list, for whom we should avoid applying imgdiff.
2320
2321 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002322 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002323 tmpdir: The directory that contains the prebuilt image and block map file.
2324 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002325 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002326 Returns:
2327 A SparseImage object, with file_map info loaded.
2328 """
Tao Baoc765cca2018-01-31 17:32:40 -08002329 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2330 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2331
2332 # The image and map files must have been created prior to calling
2333 # ota_from_target_files.py (since LMP).
2334 assert os.path.exists(path) and os.path.exists(mappath)
2335
2336 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2337 # it to clobbered_blocks so that it will be written to the target
2338 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2339 clobbered_blocks = "0"
2340
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002341 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002342 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002343
2344 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2345 # if they contain all zeros. We can't reconstruct such a file from its block
2346 # list. Tag such entries accordingly. (Bug: 65213616)
2347 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002348 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002349 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002350 continue
2351
Tom Cherryd14b8952018-08-09 14:26:00 -07002352 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2353 # filename listed in system.map may contain an additional leading slash
2354 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2355 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002356 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002357 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002358 arcname = entry.lstrip('/')
2359 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002360 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002361 else:
2362 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002363
2364 assert arcname in input_zip.namelist(), \
2365 "Failed to find the ZIP entry for {}".format(entry)
2366
Tao Baoc765cca2018-01-31 17:32:40 -08002367 info = input_zip.getinfo(arcname)
2368 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002369
2370 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002371 # image, check the original block list to determine its completeness. Note
2372 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002373 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002374 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002375
Tao Baoc765cca2018-01-31 17:32:40 -08002376 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2377 ranges.extra['incomplete'] = True
2378
2379 return image
2380
2381
Doug Zongkereef39442009-04-02 12:14:19 -07002382def GetKeyPasswords(keylist):
2383 """Given a list of keys, prompt the user to enter passwords for
2384 those which require them. Return a {key: password} dict. password
2385 will be None if the key has no password."""
2386
Doug Zongker8ce7c252009-05-22 13:34:54 -07002387 no_passwords = []
2388 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002389 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002390 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002391
2392 # sorted() can't compare strings to None, so convert Nones to strings
2393 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002394 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002395 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002396 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002397 continue
2398
T.R. Fullhart37e10522013-03-18 10:31:26 -07002399 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002400 "-inform", "DER", "-nocrypt"],
2401 stdin=devnull.fileno(),
2402 stdout=devnull.fileno(),
2403 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002404 p.communicate()
2405 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002406 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002407 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002408 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002409 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2410 "-inform", "DER", "-passin", "pass:"],
2411 stdin=devnull.fileno(),
2412 stdout=devnull.fileno(),
2413 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002414 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002415 if p.returncode == 0:
2416 # Encrypted key with empty string as password.
2417 key_passwords[k] = ''
2418 elif stderr.startswith('Error decrypting key'):
2419 # Definitely encrypted key.
2420 # It would have said "Error reading key" if it didn't parse correctly.
2421 need_passwords.append(k)
2422 else:
2423 # Potentially, a type of key that openssl doesn't understand.
2424 # We'll let the routines in signapk.jar handle it.
2425 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002426 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002427
T.R. Fullhart37e10522013-03-18 10:31:26 -07002428 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002429 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002430 return key_passwords
2431
2432
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002433def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002434 """Gets the minSdkVersion declared in the APK.
2435
Martin Stjernholm58472e82022-01-07 22:08:47 +00002436 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2437 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002438
2439 Args:
2440 apk_name: The APK filename.
2441
2442 Returns:
2443 The parsed SDK version string.
2444
2445 Raises:
2446 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002447 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002448 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002449 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002450 stderr=subprocess.PIPE)
2451 stdoutdata, stderrdata = proc.communicate()
2452 if proc.returncode != 0:
2453 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002454 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2455 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002456
Tao Baof47bf0f2018-03-21 23:28:51 -07002457 for line in stdoutdata.split("\n"):
James Wuc5e321a2023-08-01 17:45:35 +00002458 # Due to ag/24161708, looking for lines such as minSdkVersion:'23',minSdkVersion:'M'
2459 # or sdkVersion:'23', sdkVersion:'M'.
2460 m = re.match(r'(?:minSdkVersion|sdkVersion):\'([^\']*)\'', line)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002461 if m:
2462 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002463 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002464
2465
2466def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002467 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002468
Tao Baof47bf0f2018-03-21 23:28:51 -07002469 If minSdkVersion is set to a codename, it is translated to a number using the
2470 provided map.
2471
2472 Args:
2473 apk_name: The APK filename.
2474
2475 Returns:
2476 The parsed SDK version number.
2477
2478 Raises:
2479 ExternalError: On failing to get the min SDK version number.
2480 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002481 version = GetMinSdkVersion(apk_name)
2482 try:
2483 return int(version)
2484 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002485 # Not a decimal number.
2486 #
2487 # It could be either a straight codename, e.g.
2488 # UpsideDownCake
2489 #
2490 # Or a codename with API fingerprint SHA, e.g.
2491 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2492 #
2493 # Extract the codename and try and map it to a version number.
2494 split = version.split(".")
2495 codename = split[0]
2496 if codename in codename_to_api_level_map:
2497 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002498 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002499 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2500 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002501
2502
2503def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002504 codename_to_api_level_map=None, whole_file=False,
2505 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002506 """Sign the input_name zip/jar/apk, producing output_name. Use the
2507 given key and password (the latter may be None if the key does not
2508 have a password.
2509
Doug Zongker951495f2009-08-14 12:44:19 -07002510 If whole_file is true, use the "-w" option to SignApk to embed a
2511 signature that covers the whole file in the archive comment of the
2512 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002513
2514 min_api_level is the API Level (int) of the oldest platform this file may end
2515 up on. If not specified for an APK, the API Level is obtained by interpreting
2516 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2517
2518 codename_to_api_level_map is needed to translate the codename which may be
2519 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002520
2521 Caller may optionally specify extra args to be passed to SignApk, which
2522 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002523 """
Tao Bao76def242017-11-21 09:25:31 -08002524 if codename_to_api_level_map is None:
2525 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002526 if extra_signapk_args is None:
2527 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002528
Alex Klyubin9667b182015-12-10 13:38:50 -08002529 java_library_path = os.path.join(
2530 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2531
Tao Baoe95540e2016-11-08 12:08:53 -08002532 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2533 ["-Djava.library.path=" + java_library_path,
2534 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002535 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002536 if whole_file:
2537 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002538
2539 min_sdk_version = min_api_level
2540 if min_sdk_version is None:
2541 if not whole_file:
2542 min_sdk_version = GetMinSdkVersionInt(
2543 input_name, codename_to_api_level_map)
2544 if min_sdk_version is not None:
2545 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2546
T.R. Fullhart37e10522013-03-18 10:31:26 -07002547 cmd.extend([key + OPTIONS.public_key_suffix,
2548 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002549 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002550
Tao Bao73dd4f42018-10-04 16:25:33 -07002551 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002552 if password is not None:
2553 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002554 stdoutdata, _ = proc.communicate(password)
2555 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002556 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002557 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002558 proc.returncode, stdoutdata))
2559
Doug Zongkereef39442009-04-02 12:14:19 -07002560
Doug Zongker37974732010-09-16 17:44:38 -07002561def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002562 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002563
Tao Bao9dd909e2017-11-14 11:27:32 -08002564 For non-AVB images, raise exception if the data is too big. Print a warning
2565 if the data is nearing the maximum size.
2566
2567 For AVB images, the actual image size should be identical to the limit.
2568
2569 Args:
2570 data: A string that contains all the data for the partition.
2571 target: The partition name. The ".img" suffix is optional.
2572 info_dict: The dict to be looked up for relevant info.
2573 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002574 if target.endswith(".img"):
2575 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002576 mount_point = "/" + target
2577
Ying Wangf8824af2014-06-03 14:07:27 -07002578 fs_type = None
2579 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002580 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002581 if mount_point == "/userdata":
2582 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002583 p = info_dict["fstab"][mount_point]
2584 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002585 device = p.device
2586 if "/" in device:
2587 device = device[device.rfind("/")+1:]
Kelvin Zhang8c9166a2023-10-31 13:42:15 -07002588 limit = info_dict.get(device + "_size", 0)
2589 if isinstance(limit, str):
2590 limit = int(limit, 0)
Dan Albert8b72aef2015-03-23 19:13:21 -07002591 if not fs_type or not limit:
2592 return
Doug Zongkereef39442009-04-02 12:14:19 -07002593
Andrew Boie0f9aec82012-02-14 09:32:52 -08002594 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002595 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2596 # path.
2597 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2598 if size != limit:
2599 raise ExternalError(
2600 "Mismatching image size for %s: expected %d actual %d" % (
2601 target, limit, size))
2602 else:
2603 pct = float(size) * 100.0 / limit
2604 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2605 if pct >= 99.0:
2606 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002607
2608 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002609 logger.warning("\n WARNING: %s\n", msg)
2610 else:
2611 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002612
2613
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002614def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002615 """Parses the APK certs info from a given target-files zip.
2616
2617 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2618 tuple with the following elements: (1) a dictionary that maps packages to
2619 certs (based on the "certificate" and "private_key" attributes in the file;
2620 (2) a string representing the extension of compressed APKs in the target files
2621 (e.g ".gz", ".bro").
2622
2623 Args:
2624 tf_zip: The input target_files ZipFile (already open).
2625
2626 Returns:
2627 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2628 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2629 no compressed APKs.
2630 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002631 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002632 compressed_extension = None
2633
Tao Bao0f990332017-09-08 19:02:54 -07002634 # META/apkcerts.txt contains the info for _all_ the packages known at build
2635 # time. Filter out the ones that are not installed.
2636 installed_files = set()
2637 for name in tf_zip.namelist():
2638 basename = os.path.basename(name)
2639 if basename:
2640 installed_files.add(basename)
2641
Tao Baoda30cfa2017-12-01 16:19:46 -08002642 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002643 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002644 if not line:
2645 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002646 m = re.match(
2647 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002648 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2649 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002650 line)
2651 if not m:
2652 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002653
Tao Bao818ddf52018-01-05 11:17:34 -08002654 matches = m.groupdict()
2655 cert = matches["CERT"]
2656 privkey = matches["PRIVKEY"]
2657 name = matches["NAME"]
2658 this_compressed_extension = matches["COMPRESSED"]
2659
2660 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2661 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2662 if cert in SPECIAL_CERT_STRINGS and not privkey:
2663 certmap[name] = cert
2664 elif (cert.endswith(OPTIONS.public_key_suffix) and
2665 privkey.endswith(OPTIONS.private_key_suffix) and
2666 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2667 certmap[name] = cert[:-public_key_suffix_len]
2668 else:
2669 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2670
2671 if not this_compressed_extension:
2672 continue
2673
2674 # Only count the installed files.
2675 filename = name + '.' + this_compressed_extension
2676 if filename not in installed_files:
2677 continue
2678
2679 # Make sure that all the values in the compression map have the same
2680 # extension. We don't support multiple compression methods in the same
2681 # system image.
2682 if compressed_extension:
2683 if this_compressed_extension != compressed_extension:
2684 raise ValueError(
2685 "Multiple compressed extensions: {} vs {}".format(
2686 compressed_extension, this_compressed_extension))
2687 else:
2688 compressed_extension = this_compressed_extension
2689
2690 return (certmap,
2691 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002692
2693
Doug Zongkereef39442009-04-02 12:14:19 -07002694COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002695Global options
2696
2697 -p (--path) <dir>
2698 Prepend <dir>/bin to the list of places to search for binaries run by this
2699 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002700
Doug Zongker05d3dea2009-06-22 11:32:31 -07002701 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002702 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002703
Tao Bao30df8b42018-04-23 15:32:53 -07002704 -x (--extra) <key=value>
2705 Add a key/value pair to the 'extras' dict, which device-specific extension
2706 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002707
Doug Zongkereef39442009-04-02 12:14:19 -07002708 -v (--verbose)
2709 Show command lines being executed.
2710
2711 -h (--help)
2712 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002713
2714 --logfile <file>
2715 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002716"""
2717
Kelvin Zhang0876c412020-06-23 15:06:58 -04002718
Doug Zongkereef39442009-04-02 12:14:19 -07002719def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002720 print(docstring.rstrip("\n"))
2721 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002722
2723
2724def ParseOptions(argv,
2725 docstring,
2726 extra_opts="", extra_long_opts=(),
Kelvin Zhangc68c6b92023-11-14 10:54:50 -08002727 extra_option_handler: Iterable[OptionHandler] = None):
Doug Zongkereef39442009-04-02 12:14:19 -07002728 """Parse the options in argv and return any arguments that aren't
2729 flags. docstring is the calling module's docstring, to be displayed
2730 for errors and -h. extra_opts and extra_long_opts are for flags
2731 defined by the caller, which are processed by passing them to
2732 extra_option_handler."""
Kelvin Zhangc68c6b92023-11-14 10:54:50 -08002733 extra_long_opts = list(extra_long_opts)
2734 if not isinstance(extra_option_handler, Iterable):
2735 extra_option_handler = [extra_option_handler]
2736
2737 for handler in extra_option_handler:
2738 if isinstance(handler, OptionHandler):
2739 extra_long_opts.extend(handler.extra_long_opts)
Doug Zongkereef39442009-04-02 12:14:19 -07002740
2741 try:
2742 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002743 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002744 ["help", "verbose", "path=", "signapk_path=",
Thiébaud Weksteen62865ca2023-10-18 11:08:47 +11002745 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002746 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002747 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2748 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002749 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002750 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002751 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002752 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002753 sys.exit(2)
2754
Doug Zongkereef39442009-04-02 12:14:19 -07002755 for o, a in opts:
2756 if o in ("-h", "--help"):
2757 Usage(docstring)
2758 sys.exit()
2759 elif o in ("-v", "--verbose"):
2760 OPTIONS.verbose = True
2761 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002762 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002763 elif o in ("--signapk_path",):
2764 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002765 elif o in ("--signapk_shared_library_path",):
2766 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002767 elif o in ("--extra_signapk_args",):
2768 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002769 elif o in ("--aapt2_path",):
2770 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002771 elif o in ("--java_path",):
2772 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002773 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002774 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002775 elif o in ("--android_jar_path",):
2776 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002777 elif o in ("--public_key_suffix",):
2778 OPTIONS.public_key_suffix = a
2779 elif o in ("--private_key_suffix",):
2780 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002781 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002782 raise ValueError(
2783 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002784 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002785 raise ValueError(
2786 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002787 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002788 raise ValueError(
2789 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002790 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002791 raise ValueError(
2792 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002793 elif o in ("-s", "--device_specific"):
2794 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002795 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002796 key, value = a.split("=", 1)
2797 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002798 elif o in ("--logfile",):
2799 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002800 else:
Kelvin Zhangc68c6b92023-11-14 10:54:50 -08002801 if extra_option_handler is None:
2802 raise ValueError("unknown option \"%s\"" % (o,))
2803 success = False
2804 for handler in extra_option_handler:
2805 if isinstance(handler, OptionHandler):
2806 if handler.handler(o, a):
2807 success = True
2808 break
2809 elif handler(o, a):
2810 success = True
2811 if not success:
2812 raise ValueError("unknown option \"%s\"" % (o,))
2813
Doug Zongkereef39442009-04-02 12:14:19 -07002814
Doug Zongker85448772014-09-09 14:59:20 -07002815 if OPTIONS.search_path:
2816 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2817 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002818
2819 return args
2820
2821
Tao Bao4c851b12016-09-19 13:54:38 -07002822def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002823 """Make a temp file and add it to the list of things to be deleted
2824 when Cleanup() is called. Return the filename."""
2825 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2826 os.close(fd)
2827 OPTIONS.tempfiles.append(fn)
2828 return fn
2829
2830
Tao Bao1c830bf2017-12-25 10:43:47 -08002831def MakeTempDir(prefix='tmp', suffix=''):
2832 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2833
2834 Returns:
2835 The absolute pathname of the new directory.
2836 """
2837 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2838 OPTIONS.tempfiles.append(dir_name)
2839 return dir_name
2840
2841
Doug Zongkereef39442009-04-02 12:14:19 -07002842def Cleanup():
2843 for i in OPTIONS.tempfiles:
Kelvin Zhang22680912023-05-19 13:12:59 -07002844 if not os.path.exists(i):
2845 continue
Doug Zongkereef39442009-04-02 12:14:19 -07002846 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002847 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002848 else:
2849 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002850 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002851
2852
2853class PasswordManager(object):
2854 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002855 self.editor = os.getenv("EDITOR")
2856 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002857
2858 def GetPasswords(self, items):
2859 """Get passwords corresponding to each string in 'items',
2860 returning a dict. (The dict may have keys in addition to the
2861 values in 'items'.)
2862
2863 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2864 user edit that file to add more needed passwords. If no editor is
2865 available, or $ANDROID_PW_FILE isn't define, prompts the user
2866 interactively in the ordinary way.
2867 """
2868
2869 current = self.ReadFile()
2870
2871 first = True
2872 while True:
2873 missing = []
2874 for i in items:
2875 if i not in current or not current[i]:
2876 missing.append(i)
2877 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002878 if not missing:
2879 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002880
2881 for i in missing:
2882 current[i] = ""
2883
2884 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002885 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002886 if sys.version_info[0] >= 3:
2887 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002888 answer = raw_input("try to edit again? [y]> ").strip()
2889 if answer and answer[0] not in 'yY':
2890 raise RuntimeError("key passwords unavailable")
2891 first = False
2892
2893 current = self.UpdateAndReadFile(current)
2894
Kelvin Zhang0876c412020-06-23 15:06:58 -04002895 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002896 """Prompt the user to enter a value (password) for each key in
2897 'current' whose value is fales. Returns a new dict with all the
2898 values.
2899 """
2900 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002901 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002902 if v:
2903 result[k] = v
2904 else:
2905 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002906 result[k] = getpass.getpass(
2907 "Enter password for %s key> " % k).strip()
2908 if result[k]:
2909 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002910 return result
2911
2912 def UpdateAndReadFile(self, current):
2913 if not self.editor or not self.pwfile:
2914 return self.PromptResult(current)
2915
2916 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002917 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002918 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2919 f.write("# (Additional spaces are harmless.)\n\n")
2920
2921 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002922 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002923 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002924 f.write("[[[ %s ]]] %s\n" % (v, k))
2925 if not v and first_line is None:
2926 # position cursor on first line with no password.
2927 first_line = i + 4
2928 f.close()
2929
Tao Bao986ee862018-10-04 15:46:16 -07002930 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002931
2932 return self.ReadFile()
2933
2934 def ReadFile(self):
2935 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002936 if self.pwfile is None:
2937 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002938 try:
2939 f = open(self.pwfile, "r")
2940 for line in f:
2941 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002942 if not line or line[0] == '#':
2943 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002944 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2945 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002946 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002947 else:
2948 result[m.group(2)] = m.group(1)
2949 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002950 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002951 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002952 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002953 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002954
2955
Dan Albert8e0178d2015-01-27 15:53:15 -08002956def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2957 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002958
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002959 # http://b/18015246
2960 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2961 # for files larger than 2GiB. We can work around this by adjusting their
2962 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2963 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2964 # it isn't clear to me exactly what circumstances cause this).
2965 # `zipfile.write()` must be used directly to work around this.
2966 #
2967 # This mess can be avoided if we port to python3.
2968 saved_zip64_limit = zipfile.ZIP64_LIMIT
2969 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2970
Dan Albert8e0178d2015-01-27 15:53:15 -08002971 if compress_type is None:
2972 compress_type = zip_file.compression
2973 if arcname is None:
2974 arcname = filename
2975
2976 saved_stat = os.stat(filename)
2977
2978 try:
2979 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2980 # file to be zipped and reset it when we're done.
2981 os.chmod(filename, perms)
2982
2983 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002984 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2985 # intentional. zip stores datetimes in local time without a time zone
2986 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2987 # in the zip archive.
2988 local_epoch = datetime.datetime.fromtimestamp(0)
2989 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002990 os.utime(filename, (timestamp, timestamp))
2991
2992 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2993 finally:
2994 os.chmod(filename, saved_stat.st_mode)
2995 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00002996 zipfile.ZIP64_LIMIT = saved_zip64_limit
Dan Albert8e0178d2015-01-27 15:53:15 -08002997
2998
Tao Bao58c1b962015-05-20 09:32:18 -07002999def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07003000 compress_type=None):
3001 """Wrap zipfile.writestr() function to work around the zip64 limit.
3002
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003003 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
Tao Baof3282b42015-04-01 11:21:55 -07003004 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
3005 when calling crc32(bytes).
3006
3007 But it still works fine to write a shorter string into a large zip file.
3008 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
3009 when we know the string won't be too long.
3010 """
3011
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003012 saved_zip64_limit = zipfile.ZIP64_LIMIT
3013 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3014
Tao Baof3282b42015-04-01 11:21:55 -07003015 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
3016 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07003017 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07003018 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07003019 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08003020 else:
Tao Baof3282b42015-04-01 11:21:55 -07003021 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07003022 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
3023 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
3024 # such a case (since
3025 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
3026 # which seems to make more sense. Otherwise the entry will have 0o000 as the
3027 # permission bits. We follow the logic in Python 3 to get consistent
3028 # behavior between using the two versions.
3029 if not zinfo.external_attr:
3030 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07003031
3032 # If compress_type is given, it overrides the value in zinfo.
3033 if compress_type is not None:
3034 zinfo.compress_type = compress_type
3035
Tao Bao58c1b962015-05-20 09:32:18 -07003036 # If perms is given, it has a priority.
3037 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07003038 # If perms doesn't set the file type, mark it as a regular file.
3039 if perms & 0o770000 == 0:
3040 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07003041 zinfo.external_attr = perms << 16
3042
Tao Baof3282b42015-04-01 11:21:55 -07003043 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07003044 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
3045
Dan Albert8b72aef2015-03-23 19:13:21 -07003046 zip_file.writestr(zinfo, data)
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003047 zipfile.ZIP64_LIMIT = saved_zip64_limit
Tao Baof3282b42015-04-01 11:21:55 -07003048
Kelvin Zhangb84d2aa2023-11-06 10:53:41 -08003049def ZipExclude(input_zip, output_zip, entries, force=False):
3050 """Deletes entries from a ZIP file.
3051
3052 Args:
3053 zip_filename: The name of the ZIP file.
3054 entries: The name of the entry, or the list of names to be deleted.
3055 """
3056 if isinstance(entries, str):
3057 entries = [entries]
3058 # If list is empty, nothing to do
3059 if not entries:
3060 shutil.copy(input_zip, output_zip)
3061 return
3062
3063 with zipfile.ZipFile(input_zip, 'r') as zin:
3064 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
3065 raise ExternalError(
3066 "Failed to delete zip entries, name not matched: %s" % entries)
3067
3068 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(input_zip))
3069 os.close(fd)
3070 cmd = ["zip2zip", "-i", input_zip, "-o", new_zipfile]
3071 for entry in entries:
3072 cmd.append("-x")
3073 cmd.append(entry)
3074 RunAndCheckOutput(cmd)
3075 os.replace(new_zipfile, output_zip)
3076
Tao Baof3282b42015-04-01 11:21:55 -07003077
Kelvin Zhang1caead02022-09-23 10:06:03 -07003078def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08003079 """Deletes entries from a ZIP file.
3080
Tao Bao89d7ab22017-12-14 17:05:33 -08003081 Args:
3082 zip_filename: The name of the ZIP file.
3083 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08003084 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07003085 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08003086 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08003087 # If list is empty, nothing to do
3088 if not entries:
3089 return
Wei Li8895f9e2022-10-10 17:13:17 -07003090
Kelvin Zhangb84d2aa2023-11-06 10:53:41 -08003091 ZipExclude(zip_filename, zip_filename, entries, force)
Tao Bao89d7ab22017-12-14 17:05:33 -08003092
3093
Kelvin Zhangf92f7f02023-04-14 21:32:54 +00003094def ZipClose(zip_file):
3095 # http://b/18015246
3096 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
3097 # central directory.
3098 saved_zip64_limit = zipfile.ZIP64_LIMIT
3099 zipfile.ZIP64_LIMIT = (1 << 32) - 1
3100
3101 zip_file.close()
3102
3103 zipfile.ZIP64_LIMIT = saved_zip64_limit
3104
3105
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003106class DeviceSpecificParams(object):
3107 module = None
3108
3109 def __init__(self, **kwargs):
3110 """Keyword arguments to the constructor become attributes of this
3111 object, which is passed to all functions in the device-specific
3112 module."""
3113 for k, v in kwargs.items():
3114 setattr(self, k, v)
3115 self.extras = OPTIONS.extras
3116
3117 if self.module is None:
3118 path = OPTIONS.device_specific
3119 if not path:
3120 return
3121 try:
3122 if os.path.isdir(path):
3123 info = imp.find_module("releasetools", [path])
3124 else:
3125 d, f = os.path.split(path)
3126 b, x = os.path.splitext(f)
3127 if x == ".py":
3128 f = b
3129 info = imp.find_module(f, [d])
3130 logger.info("loaded device-specific extensions from %s", path)
3131 self.module = imp.load_module("device_specific", *info)
3132 except ImportError:
3133 logger.info("unable to load device-specific module; assuming none")
3134
3135 def _DoCall(self, function_name, *args, **kwargs):
3136 """Call the named function in the device-specific module, passing
3137 the given args and kwargs. The first argument to the call will be
3138 the DeviceSpecific object itself. If there is no module, or the
3139 module does not define the function, return the value of the
3140 'default' kwarg (which itself defaults to None)."""
3141 if self.module is None or not hasattr(self.module, function_name):
3142 return kwargs.get("default")
3143 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
3144
3145 def FullOTA_Assertions(self):
3146 """Called after emitting the block of assertions at the top of a
3147 full OTA package. Implementations can add whatever additional
3148 assertions they like."""
3149 return self._DoCall("FullOTA_Assertions")
3150
3151 def FullOTA_InstallBegin(self):
3152 """Called at the start of full OTA installation."""
3153 return self._DoCall("FullOTA_InstallBegin")
3154
3155 def FullOTA_GetBlockDifferences(self):
3156 """Called during full OTA installation and verification.
3157 Implementation should return a list of BlockDifference objects describing
3158 the update on each additional partitions.
3159 """
3160 return self._DoCall("FullOTA_GetBlockDifferences")
3161
3162 def FullOTA_InstallEnd(self):
3163 """Called at the end of full OTA installation; typically this is
3164 used to install the image for the device's baseband processor."""
3165 return self._DoCall("FullOTA_InstallEnd")
3166
3167 def IncrementalOTA_Assertions(self):
3168 """Called after emitting the block of assertions at the top of an
3169 incremental OTA package. Implementations can add whatever
3170 additional assertions they like."""
3171 return self._DoCall("IncrementalOTA_Assertions")
3172
3173 def IncrementalOTA_VerifyBegin(self):
3174 """Called at the start of the verification phase of incremental
3175 OTA installation; additional checks can be placed here to abort
3176 the script before any changes are made."""
3177 return self._DoCall("IncrementalOTA_VerifyBegin")
3178
3179 def IncrementalOTA_VerifyEnd(self):
3180 """Called at the end of the verification phase of incremental OTA
3181 installation; additional checks can be placed here to abort the
3182 script before any changes are made."""
3183 return self._DoCall("IncrementalOTA_VerifyEnd")
3184
3185 def IncrementalOTA_InstallBegin(self):
3186 """Called at the start of incremental OTA installation (after
3187 verification is complete)."""
3188 return self._DoCall("IncrementalOTA_InstallBegin")
3189
3190 def IncrementalOTA_GetBlockDifferences(self):
3191 """Called during incremental OTA installation and verification.
3192 Implementation should return a list of BlockDifference objects describing
3193 the update on each additional partitions.
3194 """
3195 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3196
3197 def IncrementalOTA_InstallEnd(self):
3198 """Called at the end of incremental OTA installation; typically
3199 this is used to install the image for the device's baseband
3200 processor."""
3201 return self._DoCall("IncrementalOTA_InstallEnd")
3202
3203 def VerifyOTA_Assertions(self):
3204 return self._DoCall("VerifyOTA_Assertions")
3205
3206
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003207class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003208 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003209 self.name = name
3210 self.data = data
3211 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003212 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003213 self.sha1 = sha1(data).hexdigest()
3214
3215 @classmethod
3216 def FromLocalFile(cls, name, diskname):
3217 f = open(diskname, "rb")
3218 data = f.read()
3219 f.close()
3220 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003221
3222 def WriteToTemp(self):
3223 t = tempfile.NamedTemporaryFile()
3224 t.write(self.data)
3225 t.flush()
3226 return t
3227
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003228 def WriteToDir(self, d):
3229 with open(os.path.join(d, self.name), "wb") as fp:
3230 fp.write(self.data)
3231
Geremy Condra36bd3652014-02-06 19:45:10 -08003232 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003233 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003234
Tao Bao76def242017-11-21 09:25:31 -08003235
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003236DIFF_PROGRAM_BY_EXT = {
3237 ".gz": "imgdiff",
3238 ".zip": ["imgdiff", "-z"],
3239 ".jar": ["imgdiff", "-z"],
3240 ".apk": ["imgdiff", "-z"],
3241 ".img": "imgdiff",
3242}
3243
3244
3245class Difference(object):
3246 def __init__(self, tf, sf, diff_program=None):
3247 self.tf = tf
3248 self.sf = sf
3249 self.patch = None
3250 self.diff_program = diff_program
3251
3252 def ComputePatch(self):
3253 """Compute the patch (as a string of data) needed to turn sf into
3254 tf. Returns the same tuple as GetPatch()."""
3255
3256 tf = self.tf
3257 sf = self.sf
3258
3259 if self.diff_program:
3260 diff_program = self.diff_program
3261 else:
3262 ext = os.path.splitext(tf.name)[1]
3263 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
3264
3265 ttemp = tf.WriteToTemp()
3266 stemp = sf.WriteToTemp()
3267
3268 ext = os.path.splitext(tf.name)[1]
3269
3270 try:
3271 ptemp = tempfile.NamedTemporaryFile()
3272 if isinstance(diff_program, list):
3273 cmd = copy.copy(diff_program)
3274 else:
3275 cmd = [diff_program]
3276 cmd.append(stemp.name)
3277 cmd.append(ttemp.name)
3278 cmd.append(ptemp.name)
3279 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3280 err = []
3281
3282 def run():
3283 _, e = p.communicate()
3284 if e:
3285 err.append(e)
3286 th = threading.Thread(target=run)
3287 th.start()
3288 th.join(timeout=300) # 5 mins
3289 if th.is_alive():
3290 logger.warning("diff command timed out")
3291 p.terminate()
3292 th.join(5)
3293 if th.is_alive():
3294 p.kill()
3295 th.join()
3296
3297 if p.returncode != 0:
3298 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
3299 self.patch = None
3300 return None, None, None
3301 diff = ptemp.read()
3302 finally:
3303 ptemp.close()
3304 stemp.close()
3305 ttemp.close()
3306
3307 self.patch = diff
3308 return self.tf, self.sf, self.patch
3309
3310 def GetPatch(self):
3311 """Returns a tuple of (target_file, source_file, patch_data).
3312
3313 patch_data may be None if ComputePatch hasn't been called, or if
3314 computing the patch failed.
3315 """
3316 return self.tf, self.sf, self.patch
3317
3318
3319def ComputeDifferences(diffs):
3320 """Call ComputePatch on all the Difference objects in 'diffs'."""
3321 logger.info("%d diffs to compute", len(diffs))
3322
3323 # Do the largest files first, to try and reduce the long-pole effect.
3324 by_size = [(i.tf.size, i) for i in diffs]
3325 by_size.sort(reverse=True)
3326 by_size = [i[1] for i in by_size]
3327
3328 lock = threading.Lock()
3329 diff_iter = iter(by_size) # accessed under lock
3330
3331 def worker():
3332 try:
3333 lock.acquire()
3334 for d in diff_iter:
3335 lock.release()
3336 start = time.time()
3337 d.ComputePatch()
3338 dur = time.time() - start
3339 lock.acquire()
3340
3341 tf, sf, patch = d.GetPatch()
3342 if sf.name == tf.name:
3343 name = tf.name
3344 else:
3345 name = "%s (%s)" % (tf.name, sf.name)
3346 if patch is None:
3347 logger.error("patching failed! %40s", name)
3348 else:
3349 logger.info(
3350 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3351 tf.size, 100.0 * len(patch) / tf.size, name)
3352 lock.release()
3353 except Exception:
3354 logger.exception("Failed to compute diff from worker")
3355 raise
3356
3357 # start worker threads; wait for them all to finish.
3358 threads = [threading.Thread(target=worker)
3359 for i in range(OPTIONS.worker_threads)]
3360 for th in threads:
3361 th.start()
3362 while threads:
3363 threads.pop().join()
3364
3365
3366class BlockDifference(object):
3367 def __init__(self, partition, tgt, src=None, check_first_block=False,
3368 version=None, disable_imgdiff=False):
3369 self.tgt = tgt
3370 self.src = src
3371 self.partition = partition
3372 self.check_first_block = check_first_block
3373 self.disable_imgdiff = disable_imgdiff
3374
3375 if version is None:
3376 version = max(
3377 int(i) for i in
3378 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
3379 assert version >= 3
3380 self.version = version
3381
3382 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3383 version=self.version,
3384 disable_imgdiff=self.disable_imgdiff)
3385 self.path = os.path.join(MakeTempDir(), partition)
3386 b.Compute(self.path)
3387 self._required_cache = b.max_stashed_size
3388 self.touched_src_ranges = b.touched_src_ranges
3389 self.touched_src_sha1 = b.touched_src_sha1
3390
3391 # On devices with dynamic partitions, for new partitions,
3392 # src is None but OPTIONS.source_info_dict is not.
3393 if OPTIONS.source_info_dict is None:
3394 is_dynamic_build = OPTIONS.info_dict.get(
3395 "use_dynamic_partitions") == "true"
3396 is_dynamic_source = False
3397 else:
3398 is_dynamic_build = OPTIONS.source_info_dict.get(
3399 "use_dynamic_partitions") == "true"
3400 is_dynamic_source = partition in shlex.split(
3401 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
3402
3403 is_dynamic_target = partition in shlex.split(
3404 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3405
3406 # For dynamic partitions builds, check partition list in both source
3407 # and target build because new partitions may be added, and existing
3408 # partitions may be removed.
3409 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3410
3411 if is_dynamic:
3412 self.device = 'map_partition("%s")' % partition
3413 else:
3414 if OPTIONS.source_info_dict is None:
3415 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3416 OPTIONS.info_dict)
3417 else:
3418 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3419 OPTIONS.source_info_dict)
3420 self.device = device_expr
3421
3422 @property
3423 def required_cache(self):
3424 return self._required_cache
3425
3426 def WriteScript(self, script, output_zip, progress=None,
3427 write_verify_script=False):
3428 if not self.src:
3429 # write the output unconditionally
3430 script.Print("Patching %s image unconditionally..." % (self.partition,))
3431 else:
3432 script.Print("Patching %s image after verification." % (self.partition,))
3433
3434 if progress:
3435 script.ShowProgress(progress, 0)
3436 self._WriteUpdate(script, output_zip)
3437
3438 if write_verify_script:
3439 self.WritePostInstallVerifyScript(script)
3440
3441 def WriteStrictVerifyScript(self, script):
3442 """Verify all the blocks in the care_map, including clobbered blocks.
3443
3444 This differs from the WriteVerifyScript() function: a) it prints different
3445 error messages; b) it doesn't allow half-way updated images to pass the
3446 verification."""
3447
3448 partition = self.partition
3449 script.Print("Verifying %s..." % (partition,))
3450 ranges = self.tgt.care_map
3451 ranges_str = ranges.to_string_raw()
3452 script.AppendExtra(
3453 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3454 'ui_print("%s has unexpected contents.");' % (
3455 self.device, ranges_str,
3456 self.tgt.TotalSha1(include_clobbered_blocks=True),
3457 self.partition))
3458 script.AppendExtra("")
3459
3460 def WriteVerifyScript(self, script, touched_blocks_only=False):
3461 partition = self.partition
3462
3463 # full OTA
3464 if not self.src:
3465 script.Print("Image %s will be patched unconditionally." % (partition,))
3466
3467 # incremental OTA
3468 else:
3469 if touched_blocks_only:
3470 ranges = self.touched_src_ranges
3471 expected_sha1 = self.touched_src_sha1
3472 else:
3473 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3474 expected_sha1 = self.src.TotalSha1()
3475
3476 # No blocks to be checked, skipping.
3477 if not ranges:
3478 return
3479
3480 ranges_str = ranges.to_string_raw()
3481 script.AppendExtra(
3482 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
3483 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3484 '"%s.patch.dat")) then' % (
3485 self.device, ranges_str, expected_sha1,
3486 self.device, partition, partition, partition))
3487 script.Print('Verified %s image...' % (partition,))
3488 script.AppendExtra('else')
3489
3490 if self.version >= 4:
3491
3492 # Bug: 21124327
3493 # When generating incrementals for the system and vendor partitions in
3494 # version 4 or newer, explicitly check the first block (which contains
3495 # the superblock) of the partition to see if it's what we expect. If
3496 # this check fails, give an explicit log message about the partition
3497 # having been remounted R/W (the most likely explanation).
3498 if self.check_first_block:
3499 script.AppendExtra('check_first_block(%s);' % (self.device,))
3500
3501 # If version >= 4, try block recovery before abort update
3502 if partition == "system":
3503 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3504 else:
3505 code = ErrorCode.VENDOR_RECOVER_FAILURE
3506 script.AppendExtra((
3507 'ifelse (block_image_recover({device}, "{ranges}") && '
3508 'block_image_verify({device}, '
3509 'package_extract_file("{partition}.transfer.list"), '
3510 '"{partition}.new.dat", "{partition}.patch.dat"), '
3511 'ui_print("{partition} recovered successfully."), '
3512 'abort("E{code}: {partition} partition fails to recover"));\n'
3513 'endif;').format(device=self.device, ranges=ranges_str,
3514 partition=partition, code=code))
3515
3516 # Abort the OTA update. Note that the incremental OTA cannot be applied
3517 # even if it may match the checksum of the target partition.
3518 # a) If version < 3, operations like move and erase will make changes
3519 # unconditionally and damage the partition.
3520 # b) If version >= 3, it won't even reach here.
3521 else:
3522 if partition == "system":
3523 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3524 else:
3525 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3526 script.AppendExtra((
3527 'abort("E%d: %s partition has unexpected contents");\n'
3528 'endif;') % (code, partition))
3529
3530 def WritePostInstallVerifyScript(self, script):
3531 partition = self.partition
3532 script.Print('Verifying the updated %s image...' % (partition,))
3533 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3534 ranges = self.tgt.care_map
3535 ranges_str = ranges.to_string_raw()
3536 script.AppendExtra(
3537 'if range_sha1(%s, "%s") == "%s" then' % (
3538 self.device, ranges_str,
3539 self.tgt.TotalSha1(include_clobbered_blocks=True)))
3540
3541 # Bug: 20881595
3542 # Verify that extended blocks are really zeroed out.
3543 if self.tgt.extended:
3544 ranges_str = self.tgt.extended.to_string_raw()
3545 script.AppendExtra(
3546 'if range_sha1(%s, "%s") == "%s" then' % (
3547 self.device, ranges_str,
3548 self._HashZeroBlocks(self.tgt.extended.size())))
3549 script.Print('Verified the updated %s image.' % (partition,))
3550 if partition == "system":
3551 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3552 else:
3553 code = ErrorCode.VENDOR_NONZERO_CONTENTS
3554 script.AppendExtra(
3555 'else\n'
3556 ' abort("E%d: %s partition has unexpected non-zero contents after '
3557 'OTA update");\n'
3558 'endif;' % (code, partition))
3559 else:
3560 script.Print('Verified the updated %s image.' % (partition,))
3561
3562 if partition == "system":
3563 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3564 else:
3565 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3566
3567 script.AppendExtra(
3568 'else\n'
3569 ' abort("E%d: %s partition has unexpected contents after OTA '
3570 'update");\n'
3571 'endif;' % (code, partition))
3572
3573 def _WriteUpdate(self, script, output_zip):
3574 ZipWrite(output_zip,
3575 '{}.transfer.list'.format(self.path),
3576 '{}.transfer.list'.format(self.partition))
3577
3578 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3579 # its size. Quailty 9 almost triples the compression time but doesn't
3580 # further reduce the size too much. For a typical 1.8G system.new.dat
3581 # zip | brotli(quality 6) | brotli(quality 9)
3582 # compressed_size: 942M | 869M (~8% reduced) | 854M
3583 # compression_time: 75s | 265s | 719s
3584 # decompression_time: 15s | 25s | 25s
3585
3586 if not self.src:
3587 brotli_cmd = ['brotli', '--quality=6',
3588 '--output={}.new.dat.br'.format(self.path),
3589 '{}.new.dat'.format(self.path)]
3590 print("Compressing {}.new.dat with brotli".format(self.partition))
3591 RunAndCheckOutput(brotli_cmd)
3592
3593 new_data_name = '{}.new.dat.br'.format(self.partition)
3594 ZipWrite(output_zip,
3595 '{}.new.dat.br'.format(self.path),
3596 new_data_name,
3597 compress_type=zipfile.ZIP_STORED)
3598 else:
3599 new_data_name = '{}.new.dat'.format(self.partition)
3600 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3601
3602 ZipWrite(output_zip,
3603 '{}.patch.dat'.format(self.path),
3604 '{}.patch.dat'.format(self.partition),
3605 compress_type=zipfile.ZIP_STORED)
3606
3607 if self.partition == "system":
3608 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3609 else:
3610 code = ErrorCode.VENDOR_UPDATE_FAILURE
3611
3612 call = ('block_image_update({device}, '
3613 'package_extract_file("{partition}.transfer.list"), '
3614 '"{new_data_name}", "{partition}.patch.dat") ||\n'
3615 ' abort("E{code}: Failed to update {partition} image.");'.format(
3616 device=self.device, partition=self.partition,
3617 new_data_name=new_data_name, code=code))
3618 script.AppendExtra(script.WordWrap(call))
3619
3620 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
3621 data = source.ReadRangeSet(ranges)
3622 ctx = sha1()
3623
3624 for p in data:
3625 ctx.update(p)
3626
3627 return ctx.hexdigest()
3628
3629 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
3630 """Return the hash value for all zero blocks."""
3631 zero_block = '\x00' * 4096
3632 ctx = sha1()
3633 for _ in range(num_blocks):
3634 ctx.update(zero_block)
3635
3636 return ctx.hexdigest()
3637
3638
Tianjie Xu41976c72019-07-03 13:57:01 -07003639# Expose these two classes to support vendor-specific scripts
3640DataImage = images.DataImage
3641EmptyImage = images.EmptyImage
3642
Tao Bao76def242017-11-21 09:25:31 -08003643
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003644# map recovery.fstab's fs_types to mount/format "partition types"
3645PARTITION_TYPES = {
3646 "ext4": "EMMC",
3647 "emmc": "EMMC",
3648 "f2fs": "EMMC",
3649 "squashfs": "EMMC",
3650 "erofs": "EMMC"
3651}
3652
3653
3654def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3655 """
3656 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3657 backwards compatibility. It aborts if the fstab entry has slotselect option
3658 (unless check_no_slot is explicitly set to False).
3659 """
3660 fstab = info["fstab"]
3661 if fstab:
3662 if check_no_slot:
3663 assert not fstab[mount_point].slotselect, \
3664 "Use GetTypeAndDeviceExpr instead"
3665 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3666 fstab[mount_point].device)
3667 raise KeyError
3668
3669
3670def GetTypeAndDeviceExpr(mount_point, info):
3671 """
3672 Return the filesystem of the partition, and an edify expression that evaluates
3673 to the device at runtime.
3674 """
3675 fstab = info["fstab"]
3676 if fstab:
3677 p = fstab[mount_point]
3678 device_expr = '"%s"' % fstab[mount_point].device
3679 if p.slotselect:
3680 device_expr = 'add_slot_suffix(%s)' % device_expr
3681 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
3682 raise KeyError
3683
Yifan Hongbdb32012020-05-07 12:38:53 -07003684
3685def GetEntryForDevice(fstab, device):
3686 """
3687 Returns:
3688 The first entry in fstab whose device is the given value.
3689 """
3690 if not fstab:
3691 return None
3692 for mount_point in fstab:
3693 if fstab[mount_point].device == device:
3694 return fstab[mount_point]
3695 return None
3696
Kelvin Zhang0876c412020-06-23 15:06:58 -04003697
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003698def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003699 """Parses and converts a PEM-encoded certificate into DER-encoded.
3700
3701 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3702
3703 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003704 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003705 """
3706 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003707 save = False
3708 for line in data.split("\n"):
3709 if "--END CERTIFICATE--" in line:
3710 break
3711 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003712 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003713 if "--BEGIN CERTIFICATE--" in line:
3714 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003715 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003716 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003717
Tao Bao04e1f012018-02-04 12:13:35 -08003718
3719def ExtractPublicKey(cert):
3720 """Extracts the public key (PEM-encoded) from the given certificate file.
3721
3722 Args:
3723 cert: The certificate filename.
3724
3725 Returns:
3726 The public key string.
3727
3728 Raises:
3729 AssertionError: On non-zero return from 'openssl'.
3730 """
3731 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3732 # While openssl 1.1 writes the key into the given filename followed by '-out',
3733 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3734 # stdout instead.
3735 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3736 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3737 pubkey, stderrdata = proc.communicate()
3738 assert proc.returncode == 0, \
3739 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3740 return pubkey
3741
3742
Tao Bao1ac886e2019-06-26 11:58:22 -07003743def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003744 """Extracts the AVB public key from the given public or private key.
3745
3746 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003747 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003748 key: The input key file, which should be PEM-encoded public or private key.
3749
3750 Returns:
3751 The path to the extracted AVB public key file.
3752 """
3753 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3754 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003755 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003756 return output
3757
3758
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003759def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3760 info_dict=None):
3761 """Generates the recovery-from-boot patch and writes the script to output.
3762
3763 Most of the space in the boot and recovery images is just the kernel, which is
3764 identical for the two, so the resulting patch should be efficient. Add it to
3765 the output zip, along with a shell script that is run from init.rc on first
3766 boot to actually do the patching and install the new recovery image.
3767
3768 Args:
3769 input_dir: The top-level input directory of the target-files.zip.
3770 output_sink: The callback function that writes the result.
3771 recovery_img: File object for the recovery image.
3772 boot_img: File objects for the boot image.
3773 info_dict: A dict returned by common.LoadInfoDict() on the input
3774 target_files. Will use OPTIONS.info_dict if None has been given.
3775 """
3776 if info_dict is None:
3777 info_dict = OPTIONS.info_dict
3778
3779 full_recovery_image = info_dict.get("full_recovery_image") == "true"
3780 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3781
3782 if board_uses_vendorimage:
3783 # In this case, the output sink is rooted at VENDOR
3784 recovery_img_path = "etc/recovery.img"
3785 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3786 sh_dir = "bin"
3787 else:
3788 # In this case the output sink is rooted at SYSTEM
3789 recovery_img_path = "vendor/etc/recovery.img"
3790 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3791 sh_dir = "vendor/bin"
3792
3793 if full_recovery_image:
3794 output_sink(recovery_img_path, recovery_img.data)
3795
3796 else:
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003797 include_recovery_dtbo = info_dict.get("include_recovery_dtbo") == "true"
3798 include_recovery_acpio = info_dict.get("include_recovery_acpio") == "true"
3799 path = os.path.join(input_dir, recovery_resource_dat_path)
Yi-Yo Chiang18650c72022-10-12 18:29:14 +08003800 # Use bsdiff to handle mismatching entries (Bug: 72731506)
3801 if include_recovery_dtbo or include_recovery_acpio:
Abhishek Nigam1dfca462023-11-08 02:21:39 +00003802 diff_program = ["bsdiff"]
3803 bonus_args = ""
3804 assert not os.path.exists(path)
3805 else:
3806 diff_program = ["imgdiff"]
3807 if os.path.exists(path):
3808 diff_program.append("-b")
3809 diff_program.append(path)
3810 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
3811 else:
3812 bonus_args = ""
3813
3814 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3815 _, _, patch = d.ComputePatch()
3816 output_sink("recovery-from-boot.p", patch)
3817
3818 try:
3819 # The following GetTypeAndDevice()s need to use the path in the target
3820 # info_dict instead of source_info_dict.
3821 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3822 check_no_slot=False)
3823 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3824 check_no_slot=False)
3825 except KeyError:
3826 return
3827
3828 if full_recovery_image:
3829
3830 # Note that we use /vendor to refer to the recovery resources. This will
3831 # work for a separate vendor partition mounted at /vendor or a
3832 # /system/vendor subdirectory on the system partition, for which init will
3833 # create a symlink from /vendor to /system/vendor.
3834
3835 sh = """#!/vendor/bin/sh
3836if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3837 applypatch \\
3838 --flash /vendor/etc/recovery.img \\
3839 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3840 log -t recovery "Installing new recovery image: succeeded" || \\
3841 log -t recovery "Installing new recovery image: failed"
3842else
3843 log -t recovery "Recovery image already installed"
3844fi
3845""" % {'type': recovery_type,
3846 'device': recovery_device,
3847 'sha1': recovery_img.sha1,
3848 'size': recovery_img.size}
3849 else:
3850 sh = """#!/vendor/bin/sh
3851if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3852 applypatch %(bonus_args)s \\
3853 --patch /vendor/recovery-from-boot.p \\
3854 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3855 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3856 log -t recovery "Installing new recovery image: succeeded" || \\
3857 log -t recovery "Installing new recovery image: failed"
3858else
3859 log -t recovery "Recovery image already installed"
3860fi
3861""" % {'boot_size': boot_img.size,
3862 'boot_sha1': boot_img.sha1,
3863 'recovery_size': recovery_img.size,
3864 'recovery_sha1': recovery_img.sha1,
3865 'boot_type': boot_type,
3866 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3867 'recovery_type': recovery_type,
3868 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
3869 'bonus_args': bonus_args}
3870
3871 # The install script location moved from /system/etc to /system/bin in the L
3872 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3873 sh_location = os.path.join(sh_dir, "install-recovery.sh")
3874
3875 logger.info("putting script in %s", sh_location)
3876
3877 output_sink(sh_location, sh.encode())
3878
3879
3880class DynamicPartitionUpdate(object):
3881 def __init__(self, src_group=None, tgt_group=None, progress=None,
3882 block_difference=None):
3883 self.src_group = src_group
3884 self.tgt_group = tgt_group
3885 self.progress = progress
3886 self.block_difference = block_difference
3887
3888 @property
3889 def src_size(self):
3890 if not self.block_difference:
3891 return 0
3892 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3893
3894 @property
3895 def tgt_size(self):
3896 if not self.block_difference:
3897 return 0
3898 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3899
3900 @staticmethod
3901 def _GetSparseImageSize(img):
3902 if not img:
3903 return 0
3904 return img.blocksize * img.total_blocks
3905
3906
3907class DynamicGroupUpdate(object):
3908 def __init__(self, src_size=None, tgt_size=None):
3909 # None: group does not exist. 0: no size limits.
3910 self.src_size = src_size
3911 self.tgt_size = tgt_size
3912
3913
3914class DynamicPartitionsDifference(object):
3915 def __init__(self, info_dict, block_diffs, progress_dict=None,
3916 source_info_dict=None):
3917 if progress_dict is None:
3918 progress_dict = {}
3919
3920 self._remove_all_before_apply = False
3921 if source_info_dict is None:
3922 self._remove_all_before_apply = True
3923 source_info_dict = {}
3924
3925 block_diff_dict = collections.OrderedDict(
3926 [(e.partition, e) for e in block_diffs])
3927
3928 assert len(block_diff_dict) == len(block_diffs), \
3929 "Duplicated BlockDifference object for {}".format(
3930 [partition for partition, count in
3931 collections.Counter(e.partition for e in block_diffs).items()
3932 if count > 1])
3933
3934 self._partition_updates = collections.OrderedDict()
3935
3936 for p, block_diff in block_diff_dict.items():
3937 self._partition_updates[p] = DynamicPartitionUpdate()
3938 self._partition_updates[p].block_difference = block_diff
3939
3940 for p, progress in progress_dict.items():
3941 if p in self._partition_updates:
3942 self._partition_updates[p].progress = progress
3943
3944 tgt_groups = shlex.split(info_dict.get(
3945 "super_partition_groups", "").strip())
3946 src_groups = shlex.split(source_info_dict.get(
3947 "super_partition_groups", "").strip())
3948
3949 for g in tgt_groups:
3950 for p in shlex.split(info_dict.get(
3951 "super_%s_partition_list" % g, "").strip()):
3952 assert p in self._partition_updates, \
3953 "{} is in target super_{}_partition_list but no BlockDifference " \
3954 "object is provided.".format(p, g)
3955 self._partition_updates[p].tgt_group = g
3956
3957 for g in src_groups:
3958 for p in shlex.split(source_info_dict.get(
3959 "super_%s_partition_list" % g, "").strip()):
3960 assert p in self._partition_updates, \
3961 "{} is in source super_{}_partition_list but no BlockDifference " \
3962 "object is provided.".format(p, g)
3963 self._partition_updates[p].src_group = g
3964
3965 target_dynamic_partitions = set(shlex.split(info_dict.get(
3966 "dynamic_partition_list", "").strip()))
3967 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3968 if u.tgt_size)
3969 assert block_diffs_with_target == target_dynamic_partitions, \
3970 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3971 list(target_dynamic_partitions), list(block_diffs_with_target))
3972
3973 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3974 "dynamic_partition_list", "").strip()))
3975 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3976 if u.src_size)
3977 assert block_diffs_with_source == source_dynamic_partitions, \
3978 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3979 list(source_dynamic_partitions), list(block_diffs_with_source))
3980
3981 if self._partition_updates:
3982 logger.info("Updating dynamic partitions %s",
3983 self._partition_updates.keys())
3984
3985 self._group_updates = collections.OrderedDict()
3986
3987 for g in tgt_groups:
3988 self._group_updates[g] = DynamicGroupUpdate()
3989 self._group_updates[g].tgt_size = int(info_dict.get(
3990 "super_%s_group_size" % g, "0").strip())
3991
3992 for g in src_groups:
3993 if g not in self._group_updates:
3994 self._group_updates[g] = DynamicGroupUpdate()
3995 self._group_updates[g].src_size = int(source_info_dict.get(
3996 "super_%s_group_size" % g, "0").strip())
3997
3998 self._Compute()
3999
4000 def WriteScript(self, script, output_zip, write_verify_script=False):
4001 script.Comment('--- Start patching dynamic partitions ---')
4002 for p, u in self._partition_updates.items():
4003 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4004 script.Comment('Patch partition %s' % p)
4005 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
4006 write_verify_script=False)
4007
4008 op_list_path = MakeTempFile()
4009 with open(op_list_path, 'w') as f:
4010 for line in self._op_list:
4011 f.write('{}\n'.format(line))
4012
4013 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
4014
4015 script.Comment('Update dynamic partition metadata')
4016 script.AppendExtra('assert(update_dynamic_partitions('
4017 'package_extract_file("dynamic_partitions_op_list")));')
4018
4019 if write_verify_script:
4020 for p, u in self._partition_updates.items():
4021 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4022 u.block_difference.WritePostInstallVerifyScript(script)
4023 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
4024
4025 for p, u in self._partition_updates.items():
4026 if u.tgt_size and u.src_size <= u.tgt_size:
4027 script.Comment('Patch partition %s' % p)
4028 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
4029 write_verify_script=write_verify_script)
4030 if write_verify_script:
4031 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
4032
4033 script.Comment('--- End patching dynamic partitions ---')
4034
4035 def _Compute(self):
4036 self._op_list = list()
4037
4038 def append(line):
4039 self._op_list.append(line)
4040
4041 def comment(line):
4042 self._op_list.append("# %s" % line)
4043
4044 if self._remove_all_before_apply:
4045 comment('Remove all existing dynamic partitions and groups before '
4046 'applying full OTA')
4047 append('remove_all_groups')
4048
4049 for p, u in self._partition_updates.items():
4050 if u.src_group and not u.tgt_group:
4051 append('remove %s' % p)
4052
4053 for p, u in self._partition_updates.items():
4054 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4055 comment('Move partition %s from %s to default' % (p, u.src_group))
4056 append('move %s default' % p)
4057
4058 for p, u in self._partition_updates.items():
4059 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
4060 comment('Shrink partition %s from %d to %d' %
4061 (p, u.src_size, u.tgt_size))
4062 append('resize %s %s' % (p, u.tgt_size))
4063
4064 for g, u in self._group_updates.items():
4065 if u.src_size is not None and u.tgt_size is None:
4066 append('remove_group %s' % g)
4067 if (u.src_size is not None and u.tgt_size is not None and
4068 u.src_size > u.tgt_size):
4069 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4070 append('resize_group %s %d' % (g, u.tgt_size))
4071
4072 for g, u in self._group_updates.items():
4073 if u.src_size is None and u.tgt_size is not None:
4074 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
4075 append('add_group %s %d' % (g, u.tgt_size))
4076 if (u.src_size is not None and u.tgt_size is not None and
4077 u.src_size < u.tgt_size):
4078 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
4079 append('resize_group %s %d' % (g, u.tgt_size))
4080
4081 for p, u in self._partition_updates.items():
4082 if u.tgt_group and not u.src_group:
4083 comment('Add partition %s to group %s' % (p, u.tgt_group))
4084 append('add %s %s' % (p, u.tgt_group))
4085
4086 for p, u in self._partition_updates.items():
4087 if u.tgt_size and u.src_size < u.tgt_size:
4088 comment('Grow partition %s from %d to %d' %
4089 (p, u.src_size, u.tgt_size))
4090 append('resize %s %d' % (p, u.tgt_size))
4091
4092 for p, u in self._partition_updates.items():
4093 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
4094 comment('Move partition %s from default to %s' %
4095 (p, u.tgt_group))
4096 append('move %s %s' % (p, u.tgt_group))
4097
4098
jiajia tangf3f842b2021-03-17 21:49:44 +08004099def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08004100 """
Yifan Hong85ac5012021-01-07 14:43:46 -08004101 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08004102
4103 Args:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004104 boot_img: the boot image file. Ramdisk must be compressed with lz4 or gzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08004105
4106 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08004107 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08004108 """
Yifan Hongc65a0542021-01-07 14:21:01 -08004109 tmp_dir = MakeTempDir('boot_', suffix='.img')
4110 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04004111 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
4112 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08004113 ramdisk = os.path.join(tmp_dir, 'ramdisk')
4114 if not os.path.isfile(ramdisk):
4115 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
4116 return None
4117 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08004118 if ramdisk_format == RamdiskFormat.LZ4:
4119 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
4120 elif ramdisk_format == RamdiskFormat.GZ:
4121 with open(ramdisk, 'rb') as input_stream:
4122 with open(uncompressed_ramdisk, 'wb') as output_stream:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004123 p2 = Run(['gzip', '-d'], stdin=input_stream.fileno(),
Kelvin Zhang563750f2021-04-28 12:46:17 -04004124 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08004125 p2.wait()
4126 else:
Elliott Hughes97ad1202023-06-20 16:41:58 -07004127 logger.error('Only support lz4 or gzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08004128 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004129
4130 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
4131 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
4132 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
4133 # the host environment.
4134 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04004135 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08004136
Yifan Hongc65a0542021-01-07 14:21:01 -08004137 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
4138 prop_file = os.path.join(extracted_ramdisk, search_path)
4139 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08004140 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04004141 logger.warning(
4142 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08004143
Yifan Hong7dc51172021-01-12 11:27:39 -08004144 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08004145
Yifan Hong85ac5012021-01-07 14:43:46 -08004146 except ExternalError as e:
4147 logger.warning('Unable to get boot image build props: %s', e)
4148 return None
4149
4150
4151def GetBootImageTimestamp(boot_img):
4152 """
4153 Get timestamp from ramdisk within the boot image
4154
4155 Args:
4156 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
4157
4158 Return:
4159 An integer that corresponds to the timestamp of the boot image, or None
4160 if file has unknown format. Raise exception if an unexpected error has
4161 occurred.
4162 """
4163 prop_file = GetBootImageBuildProp(boot_img)
4164 if not prop_file:
4165 return None
4166
4167 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
4168 if props is None:
4169 return None
4170
4171 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004172 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4173 if timestamp:
4174 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004175 logger.warning(
4176 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004177 return None
4178
4179 except ExternalError as e:
4180 logger.warning('Unable to get boot image timestamp: %s', e)
4181 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004182
4183
Kelvin Zhang26390482021-11-02 14:31:10 -07004184def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004185 if not os.path.exists(filepath):
4186 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004187 with open(filepath, 'rb') as fp:
4188 # Magic for android sparse image format
4189 # https://source.android.com/devices/bootloader/images
4190 return fp.read(4) == b'\x3A\xFF\x26\xED'
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004191
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004192
Kelvin Zhang22680912023-05-19 13:12:59 -07004193def UnsparseImage(filepath, target_path=None):
4194 if not IsSparseImage(filepath):
4195 return
4196 if target_path is None:
4197 tmp_img = MakeTempFile(suffix=".img")
4198 RunAndCheckOutput(["simg2img", filepath, tmp_img])
4199 os.rename(tmp_img, filepath)
4200 else:
4201 RunAndCheckOutput(["simg2img", filepath, target_path])
4202
4203
Kelvin Zhangfcd731e2023-04-04 10:28:11 -07004204def ParseUpdateEngineConfig(path: str):
4205 """Parse the update_engine config stored in file `path`
4206 Args
4207 path: Path to update_engine_config.txt file in target_files
4208
4209 Returns
4210 A tuple of (major, minor) version number . E.g. (2, 8)
4211 """
4212 with open(path, "r") as fp:
4213 # update_engine_config.txt is only supposed to contain two lines,
4214 # PAYLOAD_MAJOR_VERSION and PAYLOAD_MINOR_VERSION. 1024 should be more than
4215 # sufficient. If the length is more than that, something is wrong.
4216 data = fp.read(1024)
4217 major = re.search(r"PAYLOAD_MAJOR_VERSION=(\d+)", data)
4218 if not major:
4219 raise ValueError(
4220 f"{path} is an invalid update_engine config, missing PAYLOAD_MAJOR_VERSION {data}")
4221 minor = re.search(r"PAYLOAD_MINOR_VERSION=(\d+)", data)
4222 if not minor:
4223 raise ValueError(
4224 f"{path} is an invalid update_engine config, missing PAYLOAD_MINOR_VERSION {data}")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07004225 return (int(major.group(1)), int(minor.group(1)))