blob: cff7542c1dbff2e583ee3bdbd4771539c92479d4 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import 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
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Kelvin Zhang27324132021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080071 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
72 if "ANDROID_HOST_OUT" in os.environ:
73 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080074 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.extra_signapk_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000076 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080078 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080079 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070080 self.public_key_suffix = ".x509.pem"
81 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070082 # use otatools built boot_signer by default
83 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070084 self.boot_signer_args = []
85 self.verity_signer_path = None
86 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Melisa Carranza Zunigae6d4fb52022-03-07 14:56:26 +0100100 self.sepolicy_name = 'sepolicy.apex'
Dan Albert8b72aef2015-03-23 19:13:21 -0700101
102
103OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700104
Tao Bao71197512018-10-11 14:08:45 -0700105# The block size that's used across the releasetools scripts.
106BLOCK_SIZE = 4096
107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800108# Values for "certificate" in apkcerts that mean special things.
109SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
110
Tao Bao5cc0abb2019-03-21 10:18:05 -0700111# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
112# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800113# descriptor into vbmeta.img. When adding a new entry here, the
114# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
115# accordingly.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000116AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Lucas Wei03230252022-04-18 16:00:40 +0800117 'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000118 'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800119
Tao Bao08c190f2019-06-03 23:07:58 -0700120# Chained VBMeta partitions.
121AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
122
Tianjie Xu861f4132018-09-12 11:49:33 -0700123# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400124PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700125 'system',
126 'vendor',
127 'product',
128 'system_ext',
129 'odm',
130 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700131 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000132 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400133]
Tianjie Xu861f4132018-09-12 11:49:33 -0700134
Yifan Hong5057b952021-01-07 14:09:57 -0800135# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000136PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800137
Yifan Hongc65a0542021-01-07 14:21:01 -0800138# See sysprop.mk. If file is moved, add new search paths here; don't remove
139# existing search paths.
140RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700141
Kelvin Zhang563750f2021-04-28 12:46:17 -0400142
Tianjie Xu209db462016-05-24 17:34:52 -0700143class ErrorCode(object):
144 """Define error_codes for failures that happen during the actual
145 update package installation.
146
147 Error codes 0-999 are reserved for failures before the package
148 installation (i.e. low battery, package verification failure).
149 Detailed code in 'bootable/recovery/error_code.h' """
150
151 SYSTEM_VERIFICATION_FAILURE = 1000
152 SYSTEM_UPDATE_FAILURE = 1001
153 SYSTEM_UNEXPECTED_CONTENTS = 1002
154 SYSTEM_NONZERO_CONTENTS = 1003
155 SYSTEM_RECOVER_FAILURE = 1004
156 VENDOR_VERIFICATION_FAILURE = 2000
157 VENDOR_UPDATE_FAILURE = 2001
158 VENDOR_UNEXPECTED_CONTENTS = 2002
159 VENDOR_NONZERO_CONTENTS = 2003
160 VENDOR_RECOVER_FAILURE = 2004
161 OEM_PROP_MISMATCH = 3000
162 FINGERPRINT_MISMATCH = 3001
163 THUMBPRINT_MISMATCH = 3002
164 OLDER_BUILD = 3003
165 DEVICE_MISMATCH = 3004
166 BAD_PATCH_FILE = 3005
167 INSUFFICIENT_CACHE_SPACE = 3006
168 TUNE_PARTITION_FAILURE = 3007
169 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800170
Tao Bao80921982018-03-21 21:02:19 -0700171
Dan Albert8b72aef2015-03-23 19:13:21 -0700172class ExternalError(RuntimeError):
173 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700174
175
Tao Bao32fcdab2018-10-12 10:30:39 -0700176def InitLogging():
177 DEFAULT_LOGGING_CONFIG = {
178 'version': 1,
179 'disable_existing_loggers': False,
180 'formatters': {
181 'standard': {
182 'format':
183 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
184 'datefmt': '%Y-%m-%d %H:%M:%S',
185 },
186 },
187 'handlers': {
188 'default': {
189 'class': 'logging.StreamHandler',
190 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700191 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 },
193 },
194 'loggers': {
195 '': {
196 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700198 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700199 }
200 }
201 }
202 env_config = os.getenv('LOGGING_CONFIG')
203 if env_config:
204 with open(env_config) as f:
205 config = json.load(f)
206 else:
207 config = DEFAULT_LOGGING_CONFIG
208
209 # Increase the logging level for verbose mode.
210 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700211 config = copy.deepcopy(config)
212 config['handlers']['default']['level'] = 'INFO'
213
214 if OPTIONS.logfile:
215 config = copy.deepcopy(config)
216 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400217 'class': 'logging.FileHandler',
218 'formatter': 'standard',
219 'level': 'INFO',
220 'mode': 'w',
221 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700222 }
223 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700224
225 logging.config.dictConfig(config)
226
227
Yifan Hong8e332ff2020-07-29 17:51:55 -0700228def SetHostToolLocation(tool_name, location):
229 OPTIONS.host_tools[tool_name] = location
230
Kelvin Zhang563750f2021-04-28 12:46:17 -0400231
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900232def FindHostToolPath(tool_name):
233 """Finds the path to the host tool.
234
235 Args:
236 tool_name: name of the tool to find
237 Returns:
238 path to the tool if found under either one of the host_tools map or under
239 the same directory as this binary is located at. If not found, tool_name
240 is returned.
241 """
242 if tool_name in OPTIONS.host_tools:
243 return OPTIONS.host_tools[tool_name]
244
245 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
246 tool_path = os.path.join(my_dir, tool_name)
247 if os.path.exists(tool_path):
248 return tool_path
249
250 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700251
Kelvin Zhang563750f2021-04-28 12:46:17 -0400252
Tao Bao39451582017-05-04 11:10:47 -0700253def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700254 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700255
Tao Bao73dd4f42018-10-04 16:25:33 -0700256 Args:
257 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700258 verbose: Whether the commands should be shown. Default to the global
259 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700260 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
261 stdin, etc. stdout and stderr will default to subprocess.PIPE and
262 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800263 universal_newlines will default to True, as most of the users in
264 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700265
266 Returns:
267 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700268 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700269 if 'stdout' not in kwargs and 'stderr' not in kwargs:
270 kwargs['stdout'] = subprocess.PIPE
271 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800272 if 'universal_newlines' not in kwargs:
273 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700274
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900275 if args:
276 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700277 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900278 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700279
Kelvin Zhang766eea72021-06-03 09:36:08 -0400280 if verbose is None:
281 verbose = OPTIONS.verbose
282
Tao Bao32fcdab2018-10-12 10:30:39 -0700283 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400284 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700285 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700286 return subprocess.Popen(args, **kwargs)
287
288
Tao Bao986ee862018-10-04 15:46:16 -0700289def RunAndCheckOutput(args, verbose=None, **kwargs):
290 """Runs the given command and returns the output.
291
292 Args:
293 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700294 verbose: Whether the commands should be shown. Default to the global
295 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700296 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
297 stdin, etc. stdout and stderr will default to subprocess.PIPE and
298 subprocess.STDOUT respectively unless caller specifies any of them.
299
300 Returns:
301 The output string.
302
303 Raises:
304 ExternalError: On non-zero exit from the command.
305 """
Tao Bao986ee862018-10-04 15:46:16 -0700306 proc = Run(args, verbose=verbose, **kwargs)
307 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800308 if output is None:
309 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700310 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400311 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700312 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700313 if proc.returncode != 0:
314 raise ExternalError(
315 "Failed to run command '{}' (exit code {}):\n{}".format(
316 args, proc.returncode, output))
317 return output
318
319
Tao Baoc765cca2018-01-31 17:32:40 -0800320def RoundUpTo4K(value):
321 rounded_up = value + 4095
322 return rounded_up - (rounded_up % 4096)
323
324
Ying Wang7e6d4e42010-12-13 16:25:36 -0800325def CloseInheritedPipes():
326 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
327 before doing other work."""
328 if platform.system() != "Darwin":
329 return
330 for d in range(3, 1025):
331 try:
332 stat = os.fstat(d)
333 if stat is not None:
334 pipebit = stat[0] & 0x1000
335 if pipebit != 0:
336 os.close(d)
337 except OSError:
338 pass
339
340
Tao Bao1c320f82019-10-04 23:25:12 -0700341class BuildInfo(object):
342 """A class that holds the information for a given build.
343
344 This class wraps up the property querying for a given source or target build.
345 It abstracts away the logic of handling OEM-specific properties, and caches
346 the commonly used properties such as fingerprint.
347
348 There are two types of info dicts: a) build-time info dict, which is generated
349 at build time (i.e. included in a target_files zip); b) OEM info dict that is
350 specified at package generation time (via command line argument
351 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
352 having "oem_fingerprint_properties" in build-time info dict), all the queries
353 would be answered based on build-time info dict only. Otherwise if using
354 OEM-specific properties, some of them will be calculated from two info dicts.
355
356 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800357 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700358
359 Attributes:
360 info_dict: The build-time info dict.
361 is_ab: Whether it's a build that uses A/B OTA.
362 oem_dicts: A list of OEM dicts.
363 oem_props: A list of OEM properties that should be read from OEM dicts; None
364 if the build doesn't use any OEM-specific property.
365 fingerprint: The fingerprint of the build, which would be calculated based
366 on OEM properties if applicable.
367 device: The device name, which could come from OEM dicts if applicable.
368 """
369
370 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
371 "ro.product.manufacturer", "ro.product.model",
372 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700373 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
374 "product", "odm", "vendor", "system_ext", "system"]
375 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
376 "product", "product_services", "odm", "vendor", "system"]
377 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700378
Tianjiefdda51d2021-05-05 14:46:35 -0700379 # The length of vbmeta digest to append to the fingerprint
380 _VBMETA_DIGEST_SIZE_USED = 8
381
382 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700383 """Initializes a BuildInfo instance with the given dicts.
384
385 Note that it only wraps up the given dicts, without making copies.
386
387 Arguments:
388 info_dict: The build-time info dict.
389 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
390 that it always uses the first dict to calculate the fingerprint or the
391 device name. The rest would be used for asserting OEM properties only
392 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700393 use_legacy_id: Use the legacy build id to construct the fingerprint. This
394 is used when we need a BuildInfo class, while the vbmeta digest is
395 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700396
397 Raises:
398 ValueError: On invalid inputs.
399 """
400 self.info_dict = info_dict
401 self.oem_dicts = oem_dicts
402
403 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700404 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700405
Hongguang Chend7c160f2020-05-03 21:24:26 -0700406 # Skip _oem_props if oem_dicts is None to use BuildInfo in
407 # sign_target_files_apks
408 if self.oem_dicts:
409 self._oem_props = info_dict.get("oem_fingerprint_properties")
410 else:
411 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700412
Daniel Normand5fe8622020-01-08 17:01:11 -0800413 def check_fingerprint(fingerprint):
414 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
415 raise ValueError(
416 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
417 "3.2.2. Build Parameters.".format(fingerprint))
418
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800420 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800421 try:
422 fingerprint = self.CalculatePartitionFingerprint(partition)
423 check_fingerprint(fingerprint)
424 self._partition_fingerprints[partition] = fingerprint
425 except ExternalError:
426 continue
427 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800428 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800429 # need a fingerprint when creating the image.
430 self._partition_fingerprints[
431 "system_other"] = self._partition_fingerprints["system"]
432
Tao Bao1c320f82019-10-04 23:25:12 -0700433 # These two should be computed only after setting self._oem_props.
434 self._device = self.GetOemProperty("ro.product.device")
435 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800436 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700437
438 @property
439 def is_ab(self):
440 return self._is_ab
441
442 @property
443 def device(self):
444 return self._device
445
446 @property
447 def fingerprint(self):
448 return self._fingerprint
449
450 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400451 def is_vabc(self):
452 vendor_prop = self.info_dict.get("vendor.build.prop")
453 vabc_enabled = vendor_prop and \
454 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
455 return vabc_enabled
456
457 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700458 def is_android_r(self):
459 system_prop = self.info_dict.get("system.build.prop")
460 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
461
462 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700463 def is_vabc_xor(self):
464 vendor_prop = self.info_dict.get("vendor.build.prop")
465 vabc_xor_enabled = vendor_prop and \
466 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
467 return vabc_xor_enabled
468
469 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400470 def vendor_suppressed_vabc(self):
471 vendor_prop = self.info_dict.get("vendor.build.prop")
472 vabc_suppressed = vendor_prop and \
473 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
474 return vabc_suppressed and vabc_suppressed.lower() == "true"
475
476 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700477 def oem_props(self):
478 return self._oem_props
479
480 def __getitem__(self, key):
481 return self.info_dict[key]
482
483 def __setitem__(self, key, value):
484 self.info_dict[key] = value
485
486 def get(self, key, default=None):
487 return self.info_dict.get(key, default)
488
489 def items(self):
490 return self.info_dict.items()
491
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000492 def _GetRawBuildProp(self, prop, partition):
493 prop_file = '{}.build.prop'.format(
494 partition) if partition else 'build.prop'
495 partition_props = self.info_dict.get(prop_file)
496 if not partition_props:
497 return None
498 return partition_props.GetProp(prop)
499
Daniel Normand5fe8622020-01-08 17:01:11 -0800500 def GetPartitionBuildProp(self, prop, partition):
501 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800502
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000503 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000504 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000505 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800506
Daniel Normand5fe8622020-01-08 17:01:11 -0800507 # If provided a partition for this property, only look within that
508 # partition's build.prop.
509 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800510 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800511 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800512 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000513
514 prop_val = self._GetRawBuildProp(prop, partition)
515 if prop_val is not None:
516 return prop_val
517 raise ExternalError("couldn't find %s in %s.build.prop" %
518 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800519
Tao Bao1c320f82019-10-04 23:25:12 -0700520 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800521 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700522 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
523 return self._ResolveRoProductBuildProp(prop)
524
Tianjiefdda51d2021-05-05 14:46:35 -0700525 if prop == "ro.build.id":
526 return self._GetBuildId()
527
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000528 prop_val = self._GetRawBuildProp(prop, None)
529 if prop_val is not None:
530 return prop_val
531
532 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700533
534 def _ResolveRoProductBuildProp(self, prop):
535 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000536 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700537 if prop_val:
538 return prop_val
539
Steven Laver8e2086e2020-04-27 16:26:31 -0700540 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000541 source_order_val = self._GetRawBuildProp(
542 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700543 if source_order_val:
544 source_order = source_order_val.split(",")
545 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700546 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700547
548 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700549 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700550 raise ExternalError(
551 "Invalid ro.product.property_source_order '{}'".format(source_order))
552
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000553 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700554 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000555 "ro.product", "ro.product.{}".format(source_partition), 1)
556 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700557 if prop_val:
558 return prop_val
559
560 raise ExternalError("couldn't resolve {}".format(prop))
561
Steven Laver8e2086e2020-04-27 16:26:31 -0700562 def _GetRoProductPropsDefaultSourceOrder(self):
563 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
564 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000565 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700566 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000567 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700568 if android_version == "10":
569 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
570 # NOTE: float() conversion of android_version will have rounding error.
571 # We are checking for "9" or less, and using "< 10" is well outside of
572 # possible floating point rounding.
573 try:
574 android_version_val = float(android_version)
575 except ValueError:
576 android_version_val = 0
577 if android_version_val < 10:
578 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
579 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
580
Tianjieb37c5be2020-10-15 21:27:10 -0700581 def _GetPlatformVersion(self):
582 version_sdk = self.GetBuildProp("ro.build.version.sdk")
583 # init code switches to version_release_or_codename (see b/158483506). After
584 # API finalization, release_or_codename will be the same as release. This
585 # is the best effort to support pre-S dev stage builds.
586 if int(version_sdk) >= 30:
587 try:
588 return self.GetBuildProp("ro.build.version.release_or_codename")
589 except ExternalError:
590 logger.warning('Failed to find ro.build.version.release_or_codename')
591
592 return self.GetBuildProp("ro.build.version.release")
593
Tianjiefdda51d2021-05-05 14:46:35 -0700594 def _GetBuildId(self):
595 build_id = self._GetRawBuildProp("ro.build.id", None)
596 if build_id:
597 return build_id
598
599 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
600 if not legacy_build_id:
601 raise ExternalError("Couldn't find build id in property file")
602
603 if self.use_legacy_id:
604 return legacy_build_id
605
606 # Append the top 8 chars of vbmeta digest to the existing build id. The
607 # logic needs to match the one in init, so that OTA can deliver correctly.
608 avb_enable = self.info_dict.get("avb_enable") == "true"
609 if not avb_enable:
610 raise ExternalError("AVB isn't enabled when using legacy build id")
611
612 vbmeta_digest = self.info_dict.get("vbmeta_digest")
613 if not vbmeta_digest:
614 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
615 " id")
616 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
617 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
618
619 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
620 return legacy_build_id + '.' + digest_prefix
621
Tianjieb37c5be2020-10-15 21:27:10 -0700622 def _GetPartitionPlatformVersion(self, partition):
623 try:
624 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
625 partition)
626 except ExternalError:
627 return self.GetPartitionBuildProp("ro.build.version.release",
628 partition)
629
Tao Bao1c320f82019-10-04 23:25:12 -0700630 def GetOemProperty(self, key):
631 if self.oem_props is not None and key in self.oem_props:
632 return self.oem_dicts[0][key]
633 return self.GetBuildProp(key)
634
Daniel Normand5fe8622020-01-08 17:01:11 -0800635 def GetPartitionFingerprint(self, partition):
636 return self._partition_fingerprints.get(partition, None)
637
638 def CalculatePartitionFingerprint(self, partition):
639 try:
640 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
641 except ExternalError:
642 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
643 self.GetPartitionBuildProp("ro.product.brand", partition),
644 self.GetPartitionBuildProp("ro.product.name", partition),
645 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700646 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800647 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400648 self.GetPartitionBuildProp(
649 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800650 self.GetPartitionBuildProp("ro.build.type", partition),
651 self.GetPartitionBuildProp("ro.build.tags", partition))
652
Tao Bao1c320f82019-10-04 23:25:12 -0700653 def CalculateFingerprint(self):
654 if self.oem_props is None:
655 try:
656 return self.GetBuildProp("ro.build.fingerprint")
657 except ExternalError:
658 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
659 self.GetBuildProp("ro.product.brand"),
660 self.GetBuildProp("ro.product.name"),
661 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700662 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700663 self.GetBuildProp("ro.build.id"),
664 self.GetBuildProp("ro.build.version.incremental"),
665 self.GetBuildProp("ro.build.type"),
666 self.GetBuildProp("ro.build.tags"))
667 return "%s/%s/%s:%s" % (
668 self.GetOemProperty("ro.product.brand"),
669 self.GetOemProperty("ro.product.name"),
670 self.GetOemProperty("ro.product.device"),
671 self.GetBuildProp("ro.build.thumbprint"))
672
673 def WriteMountOemScript(self, script):
674 assert self.oem_props is not None
675 recovery_mount_options = self.info_dict.get("recovery_mount_options")
676 script.Mount("/oem", recovery_mount_options)
677
678 def WriteDeviceAssertions(self, script, oem_no_mount):
679 # Read the property directly if not using OEM properties.
680 if not self.oem_props:
681 script.AssertDevice(self.device)
682 return
683
684 # Otherwise assert OEM properties.
685 if not self.oem_dicts:
686 raise ExternalError(
687 "No OEM file provided to answer expected assertions")
688
689 for prop in self.oem_props.split():
690 values = []
691 for oem_dict in self.oem_dicts:
692 if prop in oem_dict:
693 values.append(oem_dict[prop])
694 if not values:
695 raise ExternalError(
696 "The OEM file is missing the property %s" % (prop,))
697 script.AssertOemProperty(prop, values, oem_no_mount)
698
699
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000700def ReadFromInputFile(input_file, fn):
701 """Reads the contents of fn from input zipfile or directory."""
702 if isinstance(input_file, zipfile.ZipFile):
703 return input_file.read(fn).decode()
704 else:
705 path = os.path.join(input_file, *fn.split("/"))
706 try:
707 with open(path) as f:
708 return f.read()
709 except IOError as e:
710 if e.errno == errno.ENOENT:
711 raise KeyError(fn)
712
713
Yifan Hong10482a22021-01-07 14:38:41 -0800714def ExtractFromInputFile(input_file, fn):
715 """Extracts the contents of fn from input zipfile or directory into a file."""
716 if isinstance(input_file, zipfile.ZipFile):
717 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500718 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800719 f.write(input_file.read(fn))
720 return tmp_file
721 else:
722 file = os.path.join(input_file, *fn.split("/"))
723 if not os.path.exists(file):
724 raise KeyError(fn)
725 return file
726
Kelvin Zhang563750f2021-04-28 12:46:17 -0400727
jiajia tangf3f842b2021-03-17 21:49:44 +0800728class RamdiskFormat(object):
729 LZ4 = 1
730 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800731
Kelvin Zhang563750f2021-04-28 12:46:17 -0400732
TJ Rhoades6f488e92022-05-01 22:16:22 -0700733def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800734 if info_dict.get('lz4_ramdisks') == 'true':
735 ramdisk_format = RamdiskFormat.LZ4
736 else:
737 ramdisk_format = RamdiskFormat.GZ
738 return ramdisk_format
739
Kelvin Zhang563750f2021-04-28 12:46:17 -0400740
Tao Bao410ad8b2018-08-24 12:08:38 -0700741def LoadInfoDict(input_file, repacking=False):
742 """Loads the key/value pairs from the given input target_files.
743
Tianjiea85bdf02020-07-29 11:56:19 -0700744 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700745 checks and returns the parsed key/value pairs for to the given build. It's
746 usually called early when working on input target_files files, e.g. when
747 generating OTAs, or signing builds. Note that the function may be called
748 against an old target_files file (i.e. from past dessert releases). So the
749 property parsing needs to be backward compatible.
750
751 In a `META/misc_info.txt`, a few properties are stored as links to the files
752 in the PRODUCT_OUT directory. It works fine with the build system. However,
753 they are no longer available when (re)generating images from target_files zip.
754 When `repacking` is True, redirect these properties to the actual files in the
755 unzipped directory.
756
757 Args:
758 input_file: The input target_files file, which could be an open
759 zipfile.ZipFile instance, or a str for the dir that contains the files
760 unzipped from a target_files file.
761 repacking: Whether it's trying repack an target_files file after loading the
762 info dict (default: False). If so, it will rewrite a few loaded
763 properties (e.g. selinux_fc, root_dir) to point to the actual files in
764 target_files file. When doing repacking, `input_file` must be a dir.
765
766 Returns:
767 A dict that contains the parsed key/value pairs.
768
769 Raises:
770 AssertionError: On invalid input arguments.
771 ValueError: On malformed input values.
772 """
773 if repacking:
774 assert isinstance(input_file, str), \
775 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700776
Doug Zongkerc9253822014-02-04 12:17:58 -0800777 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000778 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800779
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700780 try:
Michael Runge6e836112014-04-15 17:40:21 -0700781 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700782 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700783 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700784
Tao Bao410ad8b2018-08-24 12:08:38 -0700785 if "recovery_api_version" not in d:
786 raise ValueError("Failed to find 'recovery_api_version'")
787 if "fstab_version" not in d:
788 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800789
Tao Bao410ad8b2018-08-24 12:08:38 -0700790 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700791 # "selinux_fc" properties should point to the file_contexts files
792 # (file_contexts.bin) under META/.
793 for key in d:
794 if key.endswith("selinux_fc"):
795 fc_basename = os.path.basename(d[key])
796 fc_config = os.path.join(input_file, "META", fc_basename)
797 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700798
Daniel Norman72c626f2019-05-13 15:58:14 -0700799 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700800
Tom Cherryd14b8952018-08-09 14:26:00 -0700801 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700802 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700803 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700804 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700805
David Anderson0ec64ac2019-12-06 12:21:18 -0800806 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700807 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000808 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800809 key_name = part_name + "_base_fs_file"
810 if key_name not in d:
811 continue
812 basename = os.path.basename(d[key_name])
813 base_fs_file = os.path.join(input_file, "META", basename)
814 if os.path.exists(base_fs_file):
815 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700816 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700817 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800818 "Failed to find %s base fs file: %s", part_name, base_fs_file)
819 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700820
Doug Zongker37974732010-09-16 17:44:38 -0700821 def makeint(key):
822 if key in d:
823 d[key] = int(d[key], 0)
824
825 makeint("recovery_api_version")
826 makeint("blocksize")
827 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700828 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700829 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700830 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700831 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800832 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700833
Steve Muckle903a1ca2020-05-07 17:32:10 -0700834 boot_images = "boot.img"
835 if "boot_images" in d:
836 boot_images = d["boot_images"]
837 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400838 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700839
Tao Bao765668f2019-10-04 22:03:00 -0700840 # Load recovery fstab if applicable.
841 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700842 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800843
Tianjie Xu861f4132018-09-12 11:49:33 -0700844 # Tries to load the build props for all partitions with care_map, including
845 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800846 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800847 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000848 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800849 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700850 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800851
Tao Bao3ed35d32019-10-07 20:48:48 -0700852 # Set up the salt (based on fingerprint) that will be used when adding AVB
853 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800854 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700855 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800856 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800857 fingerprint = build_info.GetPartitionFingerprint(partition)
858 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400859 d["avb_{}_salt".format(partition)] = sha256(
860 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700861
862 # Set the vbmeta digest if exists
863 try:
864 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
865 except KeyError:
866 pass
867
Kelvin Zhang39aea442020-08-17 11:04:25 -0400868 try:
869 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
870 except KeyError:
871 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700872 return d
873
Tao Baod1de6f32017-03-01 16:38:48 -0800874
Daniel Norman4cc9df62019-07-18 10:11:07 -0700875def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900876 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700877 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900878
Daniel Norman4cc9df62019-07-18 10:11:07 -0700879
880def LoadDictionaryFromFile(file_path):
881 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900882 return LoadDictionaryFromLines(lines)
883
884
Michael Runge6e836112014-04-15 17:40:21 -0700885def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700886 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700887 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700888 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700889 if not line or line.startswith("#"):
890 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700891 if "=" in line:
892 name, value = line.split("=", 1)
893 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700894 return d
895
Tao Baod1de6f32017-03-01 16:38:48 -0800896
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000897class PartitionBuildProps(object):
898 """The class holds the build prop of a particular partition.
899
900 This class loads the build.prop and holds the build properties for a given
901 partition. It also partially recognizes the 'import' statement in the
902 build.prop; and calculates alternative values of some specific build
903 properties during runtime.
904
905 Attributes:
906 input_file: a zipped target-file or an unzipped target-file directory.
907 partition: name of the partition.
908 props_allow_override: a list of build properties to search for the
909 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000910 build_props: a dict of build properties for the given partition.
911 prop_overrides: a set of props that are overridden by import.
912 placeholder_values: A dict of runtime variables' values to replace the
913 placeholders in the build.prop file. We expect exactly one value for
914 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800915 ramdisk_format: If name is "boot", the format of ramdisk inside the
916 boot image. Otherwise, its value is ignored.
917 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000918 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400919
Tianjie Xu9afb2212020-05-10 21:48:15 +0000920 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000921 self.input_file = input_file
922 self.partition = name
923 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000924 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000925 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 self.prop_overrides = set()
927 self.placeholder_values = {}
928 if placeholder_values:
929 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000930
931 @staticmethod
932 def FromDictionary(name, build_props):
933 """Constructs an instance from a build prop dictionary."""
934
935 props = PartitionBuildProps("unknown", name)
936 props.build_props = build_props.copy()
937 return props
938
939 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800940 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000941 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800942
Devin Mooreafdd7c72021-12-13 22:04:08 +0000943 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400944 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000945 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800946 else:
947 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
948
949 props = PartitionBuildProps(input_file, name, placeholder_values)
950 props._LoadBuildProp(data)
951 return props
952
953 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000954 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800955 """
956 Read build.prop for boot image from input_file.
957 Return empty string if not found.
958 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000959 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800960 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000961 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800962 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000963 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800964 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800965 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800966 if prop_file is None:
967 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500968 with open(prop_file, "r") as f:
969 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800970
971 @staticmethod
972 def _ReadPartitionPropFile(input_file, name):
973 """
974 Read build.prop for name from input_file.
975 Return empty string if not found.
976 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000977 data = ''
978 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
979 '{}/build.prop'.format(name.upper())]:
980 try:
981 data = ReadFromInputFile(input_file, prop_file)
982 break
983 except KeyError:
984 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800985 if data == '':
986 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800987 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000988
Yifan Hong125d0b62020-09-24 17:07:03 -0700989 @staticmethod
990 def FromBuildPropFile(name, build_prop_file):
991 """Constructs an instance from a build prop file."""
992
993 props = PartitionBuildProps("unknown", name)
994 with open(build_prop_file) as f:
995 props._LoadBuildProp(f.read())
996 return props
997
Tianjie Xu9afb2212020-05-10 21:48:15 +0000998 def _LoadBuildProp(self, data):
999 for line in data.split('\n'):
1000 line = line.strip()
1001 if not line or line.startswith("#"):
1002 continue
1003 if line.startswith("import"):
1004 overrides = self._ImportParser(line)
1005 duplicates = self.prop_overrides.intersection(overrides.keys())
1006 if duplicates:
1007 raise ValueError('prop {} is overridden multiple times'.format(
1008 ','.join(duplicates)))
1009 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1010 self.build_props.update(overrides)
1011 elif "=" in line:
1012 name, value = line.split("=", 1)
1013 if name in self.prop_overrides:
1014 raise ValueError('prop {} is set again after overridden by import '
1015 'statement'.format(name))
1016 self.build_props[name] = value
1017
1018 def _ImportParser(self, line):
1019 """Parses the build prop in a given import statement."""
1020
1021 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001022 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001023 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001024
1025 if len(tokens) == 3:
1026 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1027 return {}
1028
Tianjie Xu9afb2212020-05-10 21:48:15 +00001029 import_path = tokens[1]
1030 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001031 logger.warn('Unrecognized import path {}'.format(line))
1032 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001033
1034 # We only recognize a subset of import statement that the init process
1035 # supports. And we can loose the restriction based on how the dynamic
1036 # fingerprint is used in practice. The placeholder format should be
1037 # ${placeholder}, and its value should be provided by the caller through
1038 # the placeholder_values.
1039 for prop, value in self.placeholder_values.items():
1040 prop_place_holder = '${{{}}}'.format(prop)
1041 if prop_place_holder in import_path:
1042 import_path = import_path.replace(prop_place_holder, value)
1043 if '$' in import_path:
1044 logger.info('Unresolved place holder in import path %s', import_path)
1045 return {}
1046
1047 import_path = import_path.replace('/{}'.format(self.partition),
1048 self.partition.upper())
1049 logger.info('Parsing build props override from %s', import_path)
1050
1051 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1052 d = LoadDictionaryFromLines(lines)
1053 return {key: val for key, val in d.items()
1054 if key in self.props_allow_override}
1055
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001056 def GetProp(self, prop):
1057 return self.build_props.get(prop)
1058
1059
Tianjie Xucfa86222016-03-07 16:31:19 -08001060def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1061 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001062 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001063 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001064 self.mount_point = mount_point
1065 self.fs_type = fs_type
1066 self.device = device
1067 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001068 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001069 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001070
1071 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001072 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001073 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001074 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001075 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001076
Tao Baod1de6f32017-03-01 16:38:48 -08001077 assert fstab_version == 2
1078
1079 d = {}
1080 for line in data.split("\n"):
1081 line = line.strip()
1082 if not line or line.startswith("#"):
1083 continue
1084
1085 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1086 pieces = line.split()
1087 if len(pieces) != 5:
1088 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1089
1090 # Ignore entries that are managed by vold.
1091 options = pieces[4]
1092 if "voldmanaged=" in options:
1093 continue
1094
1095 # It's a good line, parse it.
1096 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001097 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001098 options = options.split(",")
1099 for i in options:
1100 if i.startswith("length="):
1101 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001102 elif i == "slotselect":
1103 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001104 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001105 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001107
Tao Baod1de6f32017-03-01 16:38:48 -08001108 mount_flags = pieces[3]
1109 # Honor the SELinux context if present.
1110 context = None
1111 for i in mount_flags.split(","):
1112 if i.startswith("context="):
1113 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001114
Tao Baod1de6f32017-03-01 16:38:48 -08001115 mount_point = pieces[1]
1116 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001117 device=pieces[0], length=length, context=context,
1118 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001119
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001120 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001121 # system. Other areas assume system is always at "/system" so point /system
1122 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001123 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001124 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001125 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001126 return d
1127
1128
Tao Bao765668f2019-10-04 22:03:00 -07001129def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1130 """Finds the path to recovery fstab and loads its contents."""
1131 # recovery fstab is only meaningful when installing an update via recovery
1132 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001133 if info_dict.get('ab_update') == 'true' and \
1134 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001135 return None
1136
1137 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1138 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1139 # cases, since it may load the info_dict from an old build (e.g. when
1140 # generating incremental OTAs from that build).
1141 system_root_image = info_dict.get('system_root_image') == 'true'
1142 if info_dict.get('no_recovery') != 'true':
1143 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1144 if isinstance(input_file, zipfile.ZipFile):
1145 if recovery_fstab_path not in input_file.namelist():
1146 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1147 else:
1148 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1149 if not os.path.exists(path):
1150 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1151 return LoadRecoveryFSTab(
1152 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1153 system_root_image)
1154
1155 if info_dict.get('recovery_as_boot') == 'true':
1156 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1157 if isinstance(input_file, zipfile.ZipFile):
1158 if recovery_fstab_path not in input_file.namelist():
1159 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1160 else:
1161 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1162 if not os.path.exists(path):
1163 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1164 return LoadRecoveryFSTab(
1165 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1166 system_root_image)
1167
1168 return None
1169
1170
Doug Zongker37974732010-09-16 17:44:38 -07001171def DumpInfoDict(d):
1172 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001173 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001174
Dan Albert8b72aef2015-03-23 19:13:21 -07001175
Daniel Norman55417142019-11-25 16:04:36 -08001176def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001177 """Merges dynamic partition info variables.
1178
1179 Args:
1180 framework_dict: The dictionary of dynamic partition info variables from the
1181 partial framework target files.
1182 vendor_dict: The dictionary of dynamic partition info variables from the
1183 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001184
1185 Returns:
1186 The merged dynamic partition info dictionary.
1187 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001188
1189 def uniq_concat(a, b):
1190 combined = set(a.split(" "))
1191 combined.update(set(b.split(" ")))
1192 combined = [item.strip() for item in combined if item.strip()]
1193 return " ".join(sorted(combined))
1194
1195 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001196 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001197 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1198
1199 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001200 # For keys-value pairs that are the same, copy to merged dict
1201 for key in vendor_dict.keys():
1202 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1203 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001204
1205 merged_dict["dynamic_partition_list"] = uniq_concat(
1206 framework_dict.get("dynamic_partition_list", ""),
1207 vendor_dict.get("dynamic_partition_list", ""))
1208
1209 # Super block devices are defined by the vendor dict.
1210 if "super_block_devices" in vendor_dict:
1211 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1212 for block_device in merged_dict["super_block_devices"].split(" "):
1213 key = "super_%s_device_size" % block_device
1214 if key not in vendor_dict:
1215 raise ValueError("Vendor dict does not contain required key %s." % key)
1216 merged_dict[key] = vendor_dict[key]
1217
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001218 # Partition groups and group sizes are defined by the vendor dict because
1219 # these values may vary for each board that uses a shared system image.
1220 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001221 for partition_group in merged_dict["super_partition_groups"].split(" "):
1222 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001223 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001224 if key not in vendor_dict:
1225 raise ValueError("Vendor dict does not contain required key %s." % key)
1226 merged_dict[key] = vendor_dict[key]
1227
1228 # Set the partition group's partition list using a concatenation of the
1229 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001230 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001231 merged_dict[key] = uniq_concat(
1232 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301233
Daniel Normanb0c75912020-09-24 14:30:21 -07001234 # Various other flags should be copied from the vendor dict, if defined.
1235 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1236 "super_metadata_device", "super_partition_error_limit",
1237 "super_partition_size"):
1238 if key in vendor_dict.keys():
1239 merged_dict[key] = vendor_dict[key]
1240
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001241 return merged_dict
1242
1243
Daniel Norman21c34f72020-11-11 17:25:50 -08001244def PartitionMapFromTargetFiles(target_files_dir):
1245 """Builds a map from partition -> path within an extracted target files directory."""
1246 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1247 possible_subdirs = {
1248 "system": ["SYSTEM"],
1249 "vendor": ["VENDOR", "SYSTEM/vendor"],
1250 "product": ["PRODUCT", "SYSTEM/product"],
1251 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1252 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1253 "vendor_dlkm": [
1254 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1255 ],
1256 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001257 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001258 }
1259 partition_map = {}
1260 for partition, subdirs in possible_subdirs.items():
1261 for subdir in subdirs:
1262 if os.path.exists(os.path.join(target_files_dir, subdir)):
1263 partition_map[partition] = subdir
1264 break
1265 return partition_map
1266
1267
Daniel Normand3351562020-10-29 12:33:11 -07001268def SharedUidPartitionViolations(uid_dict, partition_groups):
1269 """Checks for APK sharedUserIds that cross partition group boundaries.
1270
1271 This uses a single or merged build's shareduid_violation_modules.json
1272 output file, as generated by find_shareduid_violation.py or
1273 core/tasks/find-shareduid-violation.mk.
1274
1275 An error is defined as a sharedUserId that is found in a set of partitions
1276 that span more than one partition group.
1277
1278 Args:
1279 uid_dict: A dictionary created by using the standard json module to read a
1280 complete shareduid_violation_modules.json file.
1281 partition_groups: A list of groups, where each group is a list of
1282 partitions.
1283
1284 Returns:
1285 A list of error messages.
1286 """
1287 errors = []
1288 for uid, partitions in uid_dict.items():
1289 found_in_groups = [
1290 group for group in partition_groups
1291 if set(partitions.keys()) & set(group)
1292 ]
1293 if len(found_in_groups) > 1:
1294 errors.append(
1295 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1296 % (uid, ",".join(sorted(partitions.keys()))))
1297 return errors
1298
1299
Daniel Norman21c34f72020-11-11 17:25:50 -08001300def RunHostInitVerifier(product_out, partition_map):
1301 """Runs host_init_verifier on the init rc files within partitions.
1302
1303 host_init_verifier searches the etc/init path within each partition.
1304
1305 Args:
1306 product_out: PRODUCT_OUT directory, containing partition directories.
1307 partition_map: A map of partition name -> relative path within product_out.
1308 """
1309 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1310 cmd = ["host_init_verifier"]
1311 for partition, path in partition_map.items():
1312 if partition not in allowed_partitions:
1313 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1314 partition)
1315 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1316 # Add --property-contexts if the file exists on the partition.
1317 property_contexts = "%s_property_contexts" % (
1318 "plat" if partition == "system" else partition)
1319 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1320 property_contexts)
1321 if os.path.exists(property_contexts_path):
1322 cmd.append("--property-contexts=%s" % property_contexts_path)
1323 # Add the passwd file if the file exists on the partition.
1324 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1325 if os.path.exists(passwd_path):
1326 cmd.extend(["-p", passwd_path])
1327 return RunAndCheckOutput(cmd)
1328
1329
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001330def AppendAVBSigningArgs(cmd, partition):
1331 """Append signing arguments for avbtool."""
1332 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1333 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001334 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1335 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1336 if os.path.exists(new_key_path):
1337 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001338 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1339 if key_path and algorithm:
1340 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001341 avb_salt = OPTIONS.info_dict.get("avb_salt")
1342 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001343 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001344 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001345
1346
Tao Bao765668f2019-10-04 22:03:00 -07001347def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001348 """Returns the VBMeta arguments for partition.
1349
1350 It sets up the VBMeta argument by including the partition descriptor from the
1351 given 'image', or by configuring the partition as a chained partition.
1352
1353 Args:
1354 partition: The name of the partition (e.g. "system").
1355 image: The path to the partition image.
1356 info_dict: A dict returned by common.LoadInfoDict(). Will use
1357 OPTIONS.info_dict if None has been given.
1358
1359 Returns:
1360 A list of VBMeta arguments.
1361 """
1362 if info_dict is None:
1363 info_dict = OPTIONS.info_dict
1364
1365 # Check if chain partition is used.
1366 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001367 if not key_path:
1368 return ["--include_descriptors_from_image", image]
1369
1370 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1371 # into vbmeta.img. The recovery image will be configured on an independent
1372 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1373 # See details at
1374 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001375 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001376 return []
1377
1378 # Otherwise chain the partition into vbmeta.
1379 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1380 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001381
1382
Tao Bao02a08592018-07-22 12:40:45 -07001383def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1384 """Constructs and returns the arg to build or verify a chained partition.
1385
1386 Args:
1387 partition: The partition name.
1388 info_dict: The info dict to look up the key info and rollback index
1389 location.
1390 key: The key to be used for building or verifying the partition. Defaults to
1391 the key listed in info_dict.
1392
1393 Returns:
1394 A string of form "partition:rollback_index_location:key" that can be used to
1395 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001396 """
1397 if key is None:
1398 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001399 if key and not os.path.exists(key) and OPTIONS.search_path:
1400 new_key_path = os.path.join(OPTIONS.search_path, key)
1401 if os.path.exists(new_key_path):
1402 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001403 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001404 rollback_index_location = info_dict[
1405 "avb_" + partition + "_rollback_index_location"]
1406 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1407
1408
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001409def _HasGkiCertificationArgs():
1410 return ("gki_signing_key_path" in OPTIONS.info_dict and
1411 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001412
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001413
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001414def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001415 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001416 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001417
1418 if not os.path.exists(key_path) and OPTIONS.search_path:
1419 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1420 if os.path.exists(new_key_path):
1421 key_path = new_key_path
1422
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001423 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001424 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001425 raise ExternalError(
1426 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001427
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001428 output_certificate = tempfile.NamedTemporaryFile()
1429 cmd = [
1430 "generate_gki_certificate",
1431 "--name", image_name,
1432 "--algorithm", algorithm,
1433 "--key", key_path,
1434 "--output", output_certificate.name,
1435 image,
1436 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001437
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001438 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1439 signature_args = signature_args.strip()
1440 if signature_args:
1441 cmd.extend(["--additional_avb_args", signature_args])
1442
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001443 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001444 args = args.strip()
1445 if args:
1446 cmd.extend(["--additional_avb_args", args])
1447
1448 RunAndCheckOutput(cmd)
1449
1450 output_certificate.seek(os.SEEK_SET, 0)
1451 data = output_certificate.read()
1452 output_certificate.close()
1453 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001454
1455
Daniel Norman276f0622019-07-26 14:13:51 -07001456def BuildVBMeta(image_path, partitions, name, needed_partitions):
1457 """Creates a VBMeta image.
1458
1459 It generates the requested VBMeta image. The requested image could be for
1460 top-level or chained VBMeta image, which is determined based on the name.
1461
1462 Args:
1463 image_path: The output path for the new VBMeta image.
1464 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001465 values. Only valid partition names are accepted, as partitions listed
1466 in common.AVB_PARTITIONS and custom partitions listed in
1467 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001468 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1469 needed_partitions: Partitions whose descriptors should be included into the
1470 generated VBMeta image.
1471
1472 Raises:
1473 AssertionError: On invalid input args.
1474 """
1475 avbtool = OPTIONS.info_dict["avb_avbtool"]
1476 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1477 AppendAVBSigningArgs(cmd, name)
1478
Hongguang Chenf23364d2020-04-27 18:36:36 -07001479 custom_partitions = OPTIONS.info_dict.get(
1480 "avb_custom_images_partition_list", "").strip().split()
1481
Daniel Norman276f0622019-07-26 14:13:51 -07001482 for partition, path in partitions.items():
1483 if partition not in needed_partitions:
1484 continue
1485 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001486 partition in AVB_VBMETA_PARTITIONS or
1487 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001488 'Unknown partition: {}'.format(partition)
1489 assert os.path.exists(path), \
1490 'Failed to find {} for {}'.format(path, partition)
1491 cmd.extend(GetAvbPartitionArg(partition, path))
1492
1493 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1494 if args and args.strip():
1495 split_args = shlex.split(args)
1496 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001497 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001498 # as a path relative to source tree, which may not be available at the
1499 # same location when running this script (we have the input target_files
1500 # zip only). For such cases, we additionally scan other locations (e.g.
1501 # IMAGES/, RADIO/, etc) before bailing out.
1502 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001503 chained_image = split_args[index + 1]
1504 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001505 continue
1506 found = False
1507 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1508 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001509 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001510 if os.path.exists(alt_path):
1511 split_args[index + 1] = alt_path
1512 found = True
1513 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001514 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001515 cmd.extend(split_args)
1516
1517 RunAndCheckOutput(cmd)
1518
1519
jiajia tang836f76b2021-04-02 14:48:26 +08001520def _MakeRamdisk(sourcedir, fs_config_file=None,
1521 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001522 ramdisk_img = tempfile.NamedTemporaryFile()
1523
1524 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1525 cmd = ["mkbootfs", "-f", fs_config_file,
1526 os.path.join(sourcedir, "RAMDISK")]
1527 else:
1528 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1529 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001530 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001531 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001532 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001533 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001534 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001535 else:
1536 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001537
1538 p2.wait()
1539 p1.wait()
1540 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001541 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001542
1543 return ramdisk_img
1544
1545
Steve Muckle9793cf62020-04-08 18:27:00 -07001546def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001547 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001548 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001549
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001550 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001551 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1552 we are building a two-step special image (i.e. building a recovery image to
1553 be loaded into /boot in two-step OTAs).
1554
1555 Return the image data, or None if sourcedir does not appear to contains files
1556 for building the requested image.
1557 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001558
Yifan Hong63c5ca12020-10-08 11:54:02 -07001559 if info_dict is None:
1560 info_dict = OPTIONS.info_dict
1561
Steve Muckle9793cf62020-04-08 18:27:00 -07001562 # "boot" or "recovery", without extension.
1563 partition_name = os.path.basename(sourcedir).lower()
1564
Yifan Hong63c5ca12020-10-08 11:54:02 -07001565 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001566 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001567 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1568 logger.info("Excluded kernel binary from recovery image.")
1569 else:
1570 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001571 elif partition_name == "init_boot":
1572 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001573 else:
1574 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001575 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001576 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001577 return None
1578
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001579 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1580
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001581 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001582 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001583
Doug Zongkereef39442009-04-02 12:14:19 -07001584 img = tempfile.NamedTemporaryFile()
1585
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001586 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001587 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001588 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1589 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001590
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001591 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1592 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1593
Yifan Hong63c5ca12020-10-08 11:54:02 -07001594 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001595 if kernel_path is not None:
1596 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001597
Benoit Fradina45a8682014-07-14 21:00:43 +02001598 fn = os.path.join(sourcedir, "second")
1599 if os.access(fn, os.F_OK):
1600 cmd.append("--second")
1601 cmd.append(fn)
1602
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001603 fn = os.path.join(sourcedir, "dtb")
1604 if os.access(fn, os.F_OK):
1605 cmd.append("--dtb")
1606 cmd.append(fn)
1607
Doug Zongker171f1cd2009-06-15 22:36:37 -07001608 fn = os.path.join(sourcedir, "cmdline")
1609 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001610 cmd.append("--cmdline")
1611 cmd.append(open(fn).read().rstrip("\n"))
1612
1613 fn = os.path.join(sourcedir, "base")
1614 if os.access(fn, os.F_OK):
1615 cmd.append("--base")
1616 cmd.append(open(fn).read().rstrip("\n"))
1617
Ying Wang4de6b5b2010-08-25 14:29:34 -07001618 fn = os.path.join(sourcedir, "pagesize")
1619 if os.access(fn, os.F_OK):
1620 cmd.append("--pagesize")
1621 cmd.append(open(fn).read().rstrip("\n"))
1622
Steve Mucklef84668e2020-03-16 19:13:46 -07001623 if partition_name == "recovery":
1624 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301625 if not args:
1626 # Fall back to "mkbootimg_args" for recovery image
1627 # in case "recovery_mkbootimg_args" is not set.
1628 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001629 elif partition_name == "init_boot":
1630 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001631 else:
1632 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001633 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001634 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001635
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001636 args = info_dict.get("mkbootimg_version_args")
1637 if args and args.strip():
1638 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001639
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001640 if has_ramdisk:
1641 cmd.extend(["--ramdisk", ramdisk_img.name])
1642
Tao Baod95e9fd2015-03-29 23:07:41 -07001643 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001644 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001645 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001646 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001647 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001648 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001649
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001650 if partition_name == "recovery":
1651 if info_dict.get("include_recovery_dtbo") == "true":
1652 fn = os.path.join(sourcedir, "recovery_dtbo")
1653 cmd.extend(["--recovery_dtbo", fn])
1654 if info_dict.get("include_recovery_acpio") == "true":
1655 fn = os.path.join(sourcedir, "recovery_acpio")
1656 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001657
Tao Bao986ee862018-10-04 15:46:16 -07001658 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001659
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001660 if _HasGkiCertificationArgs():
1661 if not os.path.exists(img.name):
1662 raise ValueError("Cannot find GKI boot.img")
1663 if kernel_path is None or not os.path.exists(kernel_path):
1664 raise ValueError("Cannot find GKI kernel.img")
1665
1666 # Certify GKI images.
1667 boot_signature_bytes = b''
1668 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1669 boot_signature_bytes += _GenerateGkiCertificate(
1670 kernel_path, "generic_kernel")
1671
1672 BOOT_SIGNATURE_SIZE = 16 * 1024
1673 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1674 raise ValueError(
1675 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1676 boot_signature_bytes += (
1677 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1678 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1679
1680 with open(img.name, 'ab') as f:
1681 f.write(boot_signature_bytes)
1682
Tao Bao76def242017-11-21 09:25:31 -08001683 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001684 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001685 # Hard-code the path as "/boot" for two-step special recovery image (which
1686 # will be loaded into /boot during the two-step OTA).
1687 if two_step_image:
1688 path = "/boot"
1689 else:
Tao Baobf70c312017-07-11 17:27:55 -07001690 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001691 cmd = [OPTIONS.boot_signer_path]
1692 cmd.extend(OPTIONS.boot_signer_args)
1693 cmd.extend([path, img.name,
1694 info_dict["verity_key"] + ".pk8",
1695 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001696 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001697
Tao Baod95e9fd2015-03-29 23:07:41 -07001698 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001699 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001700 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001701 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001702 # We have switched from the prebuilt futility binary to using the tool
1703 # (futility-host) built from the source. Override the setting in the old
1704 # TF.zip.
1705 futility = info_dict["futility"]
1706 if futility.startswith("prebuilts/"):
1707 futility = "futility-host"
1708 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001709 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001710 info_dict["vboot_key"] + ".vbprivk",
1711 info_dict["vboot_subkey"] + ".vbprivk",
1712 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001713 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001714 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001715
Tao Baof3282b42015-04-01 11:21:55 -07001716 # Clean up the temp files.
1717 img_unsigned.close()
1718 img_keyblock.close()
1719
David Zeuthen8fecb282017-12-01 16:24:01 -05001720 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001721 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001722 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001723 if partition_name == "recovery":
1724 part_size = info_dict["recovery_size"]
1725 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001726 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001727 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001728 "--partition_size", str(part_size), "--partition_name",
1729 partition_name]
1730 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001731 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001732 if args and args.strip():
1733 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001734 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001735
1736 img.seek(os.SEEK_SET, 0)
1737 data = img.read()
1738
1739 if has_ramdisk:
1740 ramdisk_img.close()
1741 img.close()
1742
1743 return data
1744
1745
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001746def _SignBootableImage(image_path, prebuilt_name, partition_name,
1747 info_dict=None):
1748 """Performs AVB signing for a prebuilt boot.img.
1749
1750 Args:
1751 image_path: The full path of the image, e.g., /path/to/boot.img.
1752 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001753 boot-5.10.img, recovery.img or init_boot.img.
1754 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001755 info_dict: The information dict read from misc_info.txt.
1756 """
1757 if info_dict is None:
1758 info_dict = OPTIONS.info_dict
1759
1760 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1761 if info_dict.get("avb_enable") == "true":
1762 avbtool = info_dict["avb_avbtool"]
1763 if partition_name == "recovery":
1764 part_size = info_dict["recovery_size"]
1765 else:
1766 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1767
1768 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1769 "--partition_size", str(part_size), "--partition_name",
1770 partition_name]
1771 AppendAVBSigningArgs(cmd, partition_name)
1772 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1773 if args and args.strip():
1774 cmd.extend(shlex.split(args))
1775 RunAndCheckOutput(cmd)
1776
1777
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001778def HasRamdisk(partition_name, info_dict=None):
1779 """Returns true/false to see if a bootable image should have a ramdisk.
1780
1781 Args:
1782 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1783 info_dict: The information dict read from misc_info.txt.
1784 """
1785 if info_dict is None:
1786 info_dict = OPTIONS.info_dict
1787
1788 if partition_name != "boot":
1789 return True # init_boot.img or recovery.img has a ramdisk.
1790
1791 if info_dict.get("recovery_as_boot") == "true":
1792 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1793
Bowgo Tsai85578e02022-04-19 10:50:59 +08001794 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1795 return False # A GKI boot.img has no ramdisk since Android-13.
1796
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001797 if info_dict.get("system_root_image") == "true":
1798 # The ramdisk content is merged into the system.img, so there is NO
1799 # ramdisk in the boot.img or boot-<kernel version>.img.
1800 return False
1801
1802 if info_dict.get("init_boot") == "true":
1803 # The ramdisk is moved to the init_boot.img, so there is NO
1804 # ramdisk in the boot.img or boot-<kernel version>.img.
1805 return False
1806
1807 return True
1808
1809
Doug Zongkerd5131602012-08-02 14:46:42 -07001810def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001811 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001812 """Return a File object with the desired bootable image.
1813
1814 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1815 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1816 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001817
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001818 if info_dict is None:
1819 info_dict = OPTIONS.info_dict
1820
Doug Zongker55d93282011-01-25 17:03:34 -08001821 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1822 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001823 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001824 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001825
1826 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1827 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001828 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001829 return File.FromLocalFile(name, prebuilt_path)
1830
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001831 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001832 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1833 if os.path.exists(prebuilt_path):
1834 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1835 signed_img = MakeTempFile()
1836 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001837 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1838 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001839
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001840 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001841
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001842 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001843
Doug Zongker6f1d0312014-08-22 08:07:12 -07001844 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001845 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001846 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001847 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001848 if data:
1849 return File(name, data)
1850 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001851
Doug Zongkereef39442009-04-02 12:14:19 -07001852
Lucas Wei03230252022-04-18 16:00:40 +08001853def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001854 """Build a vendor boot image from the specified sourcedir.
1855
1856 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1857 turn them into a vendor boot image.
1858
1859 Return the image data, or None if sourcedir does not appear to contains files
1860 for building the requested image.
1861 """
1862
1863 if info_dict is None:
1864 info_dict = OPTIONS.info_dict
1865
1866 img = tempfile.NamedTemporaryFile()
1867
TJ Rhoades6f488e92022-05-01 22:16:22 -07001868 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001869 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001870
1871 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1872 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1873
1874 cmd = [mkbootimg]
1875
1876 fn = os.path.join(sourcedir, "dtb")
1877 if os.access(fn, os.F_OK):
Lucas Wei03230252022-04-18 16:00:40 +08001878 has_vendor_kernel_boot = (info_dict.get("vendor_kernel_boot", "").lower() == "true")
1879
1880 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1881 # Otherwise pack dtb into vendor_boot.
1882 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1883 cmd.append("--dtb")
1884 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001885
1886 fn = os.path.join(sourcedir, "vendor_cmdline")
1887 if os.access(fn, os.F_OK):
1888 cmd.append("--vendor_cmdline")
1889 cmd.append(open(fn).read().rstrip("\n"))
1890
1891 fn = os.path.join(sourcedir, "base")
1892 if os.access(fn, os.F_OK):
1893 cmd.append("--base")
1894 cmd.append(open(fn).read().rstrip("\n"))
1895
1896 fn = os.path.join(sourcedir, "pagesize")
1897 if os.access(fn, os.F_OK):
1898 cmd.append("--pagesize")
1899 cmd.append(open(fn).read().rstrip("\n"))
1900
1901 args = info_dict.get("mkbootimg_args")
1902 if args and args.strip():
1903 cmd.extend(shlex.split(args))
1904
1905 args = info_dict.get("mkbootimg_version_args")
1906 if args and args.strip():
1907 cmd.extend(shlex.split(args))
1908
1909 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1910 cmd.extend(["--vendor_boot", img.name])
1911
Devin Moore50509012021-01-13 10:45:04 -08001912 fn = os.path.join(sourcedir, "vendor_bootconfig")
1913 if os.access(fn, os.F_OK):
1914 cmd.append("--vendor_bootconfig")
1915 cmd.append(fn)
1916
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001917 ramdisk_fragment_imgs = []
1918 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1919 if os.access(fn, os.F_OK):
1920 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1921 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001922 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1923 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001924 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001925 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1926 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001927 # Use prebuilt image if found, else create ramdisk from supplied files.
1928 if os.access(fn, os.F_OK):
1929 ramdisk_fragment_pathname = fn
1930 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001931 ramdisk_fragment_root = os.path.join(
1932 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001933 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1934 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001935 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1936 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1937 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1938
Steve Mucklee1b10862019-07-10 10:49:37 -07001939 RunAndCheckOutput(cmd)
1940
1941 # AVB: if enabled, calculate and add hash.
1942 if info_dict.get("avb_enable") == "true":
1943 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001944 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001945 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001946 "--partition_size", str(part_size), "--partition_name", partition_name]
1947 AppendAVBSigningArgs(cmd, partition_name)
1948 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001949 if args and args.strip():
1950 cmd.extend(shlex.split(args))
1951 RunAndCheckOutput(cmd)
1952
1953 img.seek(os.SEEK_SET, 0)
1954 data = img.read()
1955
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001956 for f in ramdisk_fragment_imgs:
1957 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001958 ramdisk_img.close()
1959 img.close()
1960
1961 return data
1962
1963
1964def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1965 info_dict=None):
1966 """Return a File object with the desired vendor boot image.
1967
1968 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1969 the source files in 'unpack_dir'/'tree_subdir'."""
1970
1971 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1972 if os.path.exists(prebuilt_path):
1973 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1974 return File.FromLocalFile(name, prebuilt_path)
1975
1976 logger.info("building image from target_files %s...", tree_subdir)
1977
1978 if info_dict is None:
1979 info_dict = OPTIONS.info_dict
1980
Kelvin Zhang0876c412020-06-23 15:06:58 -04001981 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001982 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1983 if data:
1984 return File(name, data)
1985 return None
1986
1987
1988def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1989 info_dict=None):
1990 """Return a File object with the desired vendor kernel boot image.
1991
1992 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1993 the source files in 'unpack_dir'/'tree_subdir'."""
1994
1995 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1996 if os.path.exists(prebuilt_path):
1997 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1998 return File.FromLocalFile(name, prebuilt_path)
1999
2000 logger.info("building image from target_files %s...", tree_subdir)
2001
2002 if info_dict is None:
2003 info_dict = OPTIONS.info_dict
2004
2005 data = _BuildVendorBootImage(
2006 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002007 if data:
2008 return File(name, data)
2009 return None
2010
2011
Narayan Kamatha07bf042017-08-14 14:49:21 +01002012def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002013 """Gunzips the given gzip compressed file to a given output file."""
2014 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002015 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002016 shutil.copyfileobj(in_file, out_file)
2017
2018
Tao Bao0ff15de2019-03-20 11:26:06 -07002019def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002020 """Unzips the archive to the given directory.
2021
2022 Args:
2023 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002024 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002025 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2026 archvie. Non-matching patterns will be filtered out. If there's no match
2027 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002028 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002029 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002030 if patterns is not None:
2031 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002032 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002033 names = input_zip.namelist()
2034 filtered = [
2035 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2036
2037 # There isn't any matching files. Don't unzip anything.
2038 if not filtered:
2039 return
2040 cmd.extend(filtered)
2041
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002042 RunAndCheckOutput(cmd)
2043
2044
Daniel Norman78554ea2021-09-14 10:29:38 -07002045def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002046 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002047
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002048 Args:
2049 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2050 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2051
Daniel Norman78554ea2021-09-14 10:29:38 -07002052 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002053 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002054
Tao Bao1c830bf2017-12-25 10:43:47 -08002055 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002056 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002057 """
Doug Zongkereef39442009-04-02 12:14:19 -07002058
Tao Bao1c830bf2017-12-25 10:43:47 -08002059 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002060 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2061 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002062 UnzipToDir(m.group(1), tmp, patterns)
2063 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002064 filename = m.group(1)
2065 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002066 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002067
Tao Baodba59ee2018-01-09 13:21:02 -08002068 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002069
2070
Yifan Hong8a66a712019-04-04 15:37:57 -07002071def GetUserImage(which, tmpdir, input_zip,
2072 info_dict=None,
2073 allow_shared_blocks=None,
2074 hashtree_info_generator=None,
2075 reset_file_map=False):
2076 """Returns an Image object suitable for passing to BlockImageDiff.
2077
2078 This function loads the specified image from the given path. If the specified
2079 image is sparse, it also performs additional processing for OTA purpose. For
2080 example, it always adds block 0 to clobbered blocks list. It also detects
2081 files that cannot be reconstructed from the block list, for whom we should
2082 avoid applying imgdiff.
2083
2084 Args:
2085 which: The partition name.
2086 tmpdir: The directory that contains the prebuilt image and block map file.
2087 input_zip: The target-files ZIP archive.
2088 info_dict: The dict to be looked up for relevant info.
2089 allow_shared_blocks: If image is sparse, whether having shared blocks is
2090 allowed. If none, it is looked up from info_dict.
2091 hashtree_info_generator: If present and image is sparse, generates the
2092 hashtree_info for this sparse image.
2093 reset_file_map: If true and image is sparse, reset file map before returning
2094 the image.
2095 Returns:
2096 A Image object. If it is a sparse image and reset_file_map is False, the
2097 image will have file_map info loaded.
2098 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002099 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002100 info_dict = LoadInfoDict(input_zip)
2101
2102 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002103 if info_dict.get(which + "_disable_sparse"):
2104 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002105
2106 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2107 # shared blocks (i.e. some blocks will show up in multiple files' block
2108 # list). We can only allocate such shared blocks to the first "owner", and
2109 # disable imgdiff for all later occurrences.
2110 if allow_shared_blocks is None:
2111 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2112
2113 if is_sparse:
2114 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2115 hashtree_info_generator)
2116 if reset_file_map:
2117 img.ResetFileMap()
2118 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002119 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002120
2121
2122def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2123 """Returns a Image object suitable for passing to BlockImageDiff.
2124
2125 This function loads the specified non-sparse image from the given path.
2126
2127 Args:
2128 which: The partition name.
2129 tmpdir: The directory that contains the prebuilt image and block map file.
2130 Returns:
2131 A Image object.
2132 """
2133 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2134 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2135
2136 # The image and map files must have been created prior to calling
2137 # ota_from_target_files.py (since LMP).
2138 assert os.path.exists(path) and os.path.exists(mappath)
2139
Tianjie Xu41976c72019-07-03 13:57:01 -07002140 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2141
Yifan Hong8a66a712019-04-04 15:37:57 -07002142
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002143def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2144 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002145 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2146
2147 This function loads the specified sparse image from the given path, and
2148 performs additional processing for OTA purpose. For example, it always adds
2149 block 0 to clobbered blocks list. It also detects files that cannot be
2150 reconstructed from the block list, for whom we should avoid applying imgdiff.
2151
2152 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002153 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002154 tmpdir: The directory that contains the prebuilt image and block map file.
2155 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002156 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002157 hashtree_info_generator: If present, generates the hashtree_info for this
2158 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002159 Returns:
2160 A SparseImage object, with file_map info loaded.
2161 """
Tao Baoc765cca2018-01-31 17:32:40 -08002162 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2163 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2164
2165 # The image and map files must have been created prior to calling
2166 # ota_from_target_files.py (since LMP).
2167 assert os.path.exists(path) and os.path.exists(mappath)
2168
2169 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2170 # it to clobbered_blocks so that it will be written to the target
2171 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2172 clobbered_blocks = "0"
2173
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002174 image = sparse_img.SparseImage(
2175 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2176 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002177
2178 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2179 # if they contain all zeros. We can't reconstruct such a file from its block
2180 # list. Tag such entries accordingly. (Bug: 65213616)
2181 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002182 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002183 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002184 continue
2185
Tom Cherryd14b8952018-08-09 14:26:00 -07002186 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2187 # filename listed in system.map may contain an additional leading slash
2188 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2189 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002190 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002191 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002192 arcname = entry.lstrip('/')
2193 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002194 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002195 else:
2196 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002197
2198 assert arcname in input_zip.namelist(), \
2199 "Failed to find the ZIP entry for {}".format(entry)
2200
Tao Baoc765cca2018-01-31 17:32:40 -08002201 info = input_zip.getinfo(arcname)
2202 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002203
2204 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002205 # image, check the original block list to determine its completeness. Note
2206 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002207 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002208 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002209
Tao Baoc765cca2018-01-31 17:32:40 -08002210 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2211 ranges.extra['incomplete'] = True
2212
2213 return image
2214
2215
Doug Zongkereef39442009-04-02 12:14:19 -07002216def GetKeyPasswords(keylist):
2217 """Given a list of keys, prompt the user to enter passwords for
2218 those which require them. Return a {key: password} dict. password
2219 will be None if the key has no password."""
2220
Doug Zongker8ce7c252009-05-22 13:34:54 -07002221 no_passwords = []
2222 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002223 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002224 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002225
2226 # sorted() can't compare strings to None, so convert Nones to strings
2227 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002228 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002229 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002230 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002231 continue
2232
T.R. Fullhart37e10522013-03-18 10:31:26 -07002233 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002234 "-inform", "DER", "-nocrypt"],
2235 stdin=devnull.fileno(),
2236 stdout=devnull.fileno(),
2237 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002238 p.communicate()
2239 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002240 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002241 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002242 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002243 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2244 "-inform", "DER", "-passin", "pass:"],
2245 stdin=devnull.fileno(),
2246 stdout=devnull.fileno(),
2247 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002248 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002249 if p.returncode == 0:
2250 # Encrypted key with empty string as password.
2251 key_passwords[k] = ''
2252 elif stderr.startswith('Error decrypting key'):
2253 # Definitely encrypted key.
2254 # It would have said "Error reading key" if it didn't parse correctly.
2255 need_passwords.append(k)
2256 else:
2257 # Potentially, a type of key that openssl doesn't understand.
2258 # We'll let the routines in signapk.jar handle it.
2259 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002260 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002261
T.R. Fullhart37e10522013-03-18 10:31:26 -07002262 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002263 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002264 return key_passwords
2265
2266
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002267def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002268 """Gets the minSdkVersion declared in the APK.
2269
Martin Stjernholm58472e82022-01-07 22:08:47 +00002270 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2271 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002272
2273 Args:
2274 apk_name: The APK filename.
2275
2276 Returns:
2277 The parsed SDK version string.
2278
2279 Raises:
2280 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002281 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002282 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002283 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002284 stderr=subprocess.PIPE)
2285 stdoutdata, stderrdata = proc.communicate()
2286 if proc.returncode != 0:
2287 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002288 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2289 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002290
Tao Baof47bf0f2018-03-21 23:28:51 -07002291 for line in stdoutdata.split("\n"):
2292 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002293 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2294 if m:
2295 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002296 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002297
2298
2299def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002300 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002301
Tao Baof47bf0f2018-03-21 23:28:51 -07002302 If minSdkVersion is set to a codename, it is translated to a number using the
2303 provided map.
2304
2305 Args:
2306 apk_name: The APK filename.
2307
2308 Returns:
2309 The parsed SDK version number.
2310
2311 Raises:
2312 ExternalError: On failing to get the min SDK version number.
2313 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002314 version = GetMinSdkVersion(apk_name)
2315 try:
2316 return int(version)
2317 except ValueError:
2318 # Not a decimal number. Codename?
2319 if version in codename_to_api_level_map:
2320 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002321 raise ExternalError(
2322 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2323 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002324
2325
2326def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002327 codename_to_api_level_map=None, whole_file=False,
2328 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002329 """Sign the input_name zip/jar/apk, producing output_name. Use the
2330 given key and password (the latter may be None if the key does not
2331 have a password.
2332
Doug Zongker951495f2009-08-14 12:44:19 -07002333 If whole_file is true, use the "-w" option to SignApk to embed a
2334 signature that covers the whole file in the archive comment of the
2335 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002336
2337 min_api_level is the API Level (int) of the oldest platform this file may end
2338 up on. If not specified for an APK, the API Level is obtained by interpreting
2339 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2340
2341 codename_to_api_level_map is needed to translate the codename which may be
2342 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002343
2344 Caller may optionally specify extra args to be passed to SignApk, which
2345 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002346 """
Tao Bao76def242017-11-21 09:25:31 -08002347 if codename_to_api_level_map is None:
2348 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002349 if extra_signapk_args is None:
2350 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002351
Alex Klyubin9667b182015-12-10 13:38:50 -08002352 java_library_path = os.path.join(
2353 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2354
Tao Baoe95540e2016-11-08 12:08:53 -08002355 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2356 ["-Djava.library.path=" + java_library_path,
2357 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002358 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002359 if whole_file:
2360 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002361
2362 min_sdk_version = min_api_level
2363 if min_sdk_version is None:
2364 if not whole_file:
2365 min_sdk_version = GetMinSdkVersionInt(
2366 input_name, codename_to_api_level_map)
2367 if min_sdk_version is not None:
2368 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2369
T.R. Fullhart37e10522013-03-18 10:31:26 -07002370 cmd.extend([key + OPTIONS.public_key_suffix,
2371 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002372 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002373
Tao Bao73dd4f42018-10-04 16:25:33 -07002374 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002375 if password is not None:
2376 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002377 stdoutdata, _ = proc.communicate(password)
2378 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002379 raise ExternalError(
2380 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002381 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002382
Doug Zongkereef39442009-04-02 12:14:19 -07002383
Doug Zongker37974732010-09-16 17:44:38 -07002384def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002385 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002386
Tao Bao9dd909e2017-11-14 11:27:32 -08002387 For non-AVB images, raise exception if the data is too big. Print a warning
2388 if the data is nearing the maximum size.
2389
2390 For AVB images, the actual image size should be identical to the limit.
2391
2392 Args:
2393 data: A string that contains all the data for the partition.
2394 target: The partition name. The ".img" suffix is optional.
2395 info_dict: The dict to be looked up for relevant info.
2396 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002397 if target.endswith(".img"):
2398 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002399 mount_point = "/" + target
2400
Ying Wangf8824af2014-06-03 14:07:27 -07002401 fs_type = None
2402 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002403 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002404 if mount_point == "/userdata":
2405 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002406 p = info_dict["fstab"][mount_point]
2407 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002408 device = p.device
2409 if "/" in device:
2410 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002411 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002412 if not fs_type or not limit:
2413 return
Doug Zongkereef39442009-04-02 12:14:19 -07002414
Andrew Boie0f9aec82012-02-14 09:32:52 -08002415 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002416 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2417 # path.
2418 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2419 if size != limit:
2420 raise ExternalError(
2421 "Mismatching image size for %s: expected %d actual %d" % (
2422 target, limit, size))
2423 else:
2424 pct = float(size) * 100.0 / limit
2425 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2426 if pct >= 99.0:
2427 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002428
2429 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002430 logger.warning("\n WARNING: %s\n", msg)
2431 else:
2432 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002433
2434
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002435def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002436 """Parses the APK certs info from a given target-files zip.
2437
2438 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2439 tuple with the following elements: (1) a dictionary that maps packages to
2440 certs (based on the "certificate" and "private_key" attributes in the file;
2441 (2) a string representing the extension of compressed APKs in the target files
2442 (e.g ".gz", ".bro").
2443
2444 Args:
2445 tf_zip: The input target_files ZipFile (already open).
2446
2447 Returns:
2448 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2449 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2450 no compressed APKs.
2451 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002452 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002453 compressed_extension = None
2454
Tao Bao0f990332017-09-08 19:02:54 -07002455 # META/apkcerts.txt contains the info for _all_ the packages known at build
2456 # time. Filter out the ones that are not installed.
2457 installed_files = set()
2458 for name in tf_zip.namelist():
2459 basename = os.path.basename(name)
2460 if basename:
2461 installed_files.add(basename)
2462
Tao Baoda30cfa2017-12-01 16:19:46 -08002463 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002464 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002465 if not line:
2466 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002467 m = re.match(
2468 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002469 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2470 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002471 line)
2472 if not m:
2473 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002474
Tao Bao818ddf52018-01-05 11:17:34 -08002475 matches = m.groupdict()
2476 cert = matches["CERT"]
2477 privkey = matches["PRIVKEY"]
2478 name = matches["NAME"]
2479 this_compressed_extension = matches["COMPRESSED"]
2480
2481 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2482 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2483 if cert in SPECIAL_CERT_STRINGS and not privkey:
2484 certmap[name] = cert
2485 elif (cert.endswith(OPTIONS.public_key_suffix) and
2486 privkey.endswith(OPTIONS.private_key_suffix) and
2487 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2488 certmap[name] = cert[:-public_key_suffix_len]
2489 else:
2490 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2491
2492 if not this_compressed_extension:
2493 continue
2494
2495 # Only count the installed files.
2496 filename = name + '.' + this_compressed_extension
2497 if filename not in installed_files:
2498 continue
2499
2500 # Make sure that all the values in the compression map have the same
2501 # extension. We don't support multiple compression methods in the same
2502 # system image.
2503 if compressed_extension:
2504 if this_compressed_extension != compressed_extension:
2505 raise ValueError(
2506 "Multiple compressed extensions: {} vs {}".format(
2507 compressed_extension, this_compressed_extension))
2508 else:
2509 compressed_extension = this_compressed_extension
2510
2511 return (certmap,
2512 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002513
2514
Doug Zongkereef39442009-04-02 12:14:19 -07002515COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002516Global options
2517
2518 -p (--path) <dir>
2519 Prepend <dir>/bin to the list of places to search for binaries run by this
2520 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002521
Doug Zongker05d3dea2009-06-22 11:32:31 -07002522 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002523 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002524
Tao Bao30df8b42018-04-23 15:32:53 -07002525 -x (--extra) <key=value>
2526 Add a key/value pair to the 'extras' dict, which device-specific extension
2527 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002528
Doug Zongkereef39442009-04-02 12:14:19 -07002529 -v (--verbose)
2530 Show command lines being executed.
2531
2532 -h (--help)
2533 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002534
2535 --logfile <file>
2536 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002537"""
2538
Kelvin Zhang0876c412020-06-23 15:06:58 -04002539
Doug Zongkereef39442009-04-02 12:14:19 -07002540def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002541 print(docstring.rstrip("\n"))
2542 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002543
2544
2545def ParseOptions(argv,
2546 docstring,
2547 extra_opts="", extra_long_opts=(),
2548 extra_option_handler=None):
2549 """Parse the options in argv and return any arguments that aren't
2550 flags. docstring is the calling module's docstring, to be displayed
2551 for errors and -h. extra_opts and extra_long_opts are for flags
2552 defined by the caller, which are processed by passing them to
2553 extra_option_handler."""
2554
2555 try:
2556 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002557 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002558 ["help", "verbose", "path=", "signapk_path=",
Martin Stjernholm58472e82022-01-07 22:08:47 +00002559 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002560 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002561 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2562 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002563 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002564 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002565 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002566 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002567 sys.exit(2)
2568
Doug Zongkereef39442009-04-02 12:14:19 -07002569 for o, a in opts:
2570 if o in ("-h", "--help"):
2571 Usage(docstring)
2572 sys.exit()
2573 elif o in ("-v", "--verbose"):
2574 OPTIONS.verbose = True
2575 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002576 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002577 elif o in ("--signapk_path",):
2578 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002579 elif o in ("--signapk_shared_library_path",):
2580 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002581 elif o in ("--extra_signapk_args",):
2582 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002583 elif o in ("--aapt2_path",):
2584 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002585 elif o in ("--java_path",):
2586 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002587 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002588 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002589 elif o in ("--android_jar_path",):
2590 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002591 elif o in ("--public_key_suffix",):
2592 OPTIONS.public_key_suffix = a
2593 elif o in ("--private_key_suffix",):
2594 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002595 elif o in ("--boot_signer_path",):
2596 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002597 elif o in ("--boot_signer_args",):
2598 OPTIONS.boot_signer_args = shlex.split(a)
2599 elif o in ("--verity_signer_path",):
2600 OPTIONS.verity_signer_path = a
2601 elif o in ("--verity_signer_args",):
2602 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002603 elif o in ("-s", "--device_specific"):
2604 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002605 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002606 key, value = a.split("=", 1)
2607 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002608 elif o in ("--logfile",):
2609 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002610 else:
2611 if extra_option_handler is None or not extra_option_handler(o, a):
2612 assert False, "unknown option \"%s\"" % (o,)
2613
Doug Zongker85448772014-09-09 14:59:20 -07002614 if OPTIONS.search_path:
2615 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2616 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002617
2618 return args
2619
2620
Tao Bao4c851b12016-09-19 13:54:38 -07002621def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002622 """Make a temp file and add it to the list of things to be deleted
2623 when Cleanup() is called. Return the filename."""
2624 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2625 os.close(fd)
2626 OPTIONS.tempfiles.append(fn)
2627 return fn
2628
2629
Tao Bao1c830bf2017-12-25 10:43:47 -08002630def MakeTempDir(prefix='tmp', suffix=''):
2631 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2632
2633 Returns:
2634 The absolute pathname of the new directory.
2635 """
2636 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2637 OPTIONS.tempfiles.append(dir_name)
2638 return dir_name
2639
2640
Doug Zongkereef39442009-04-02 12:14:19 -07002641def Cleanup():
2642 for i in OPTIONS.tempfiles:
2643 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002644 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002645 else:
2646 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002647 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002648
2649
2650class PasswordManager(object):
2651 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002652 self.editor = os.getenv("EDITOR")
2653 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002654
2655 def GetPasswords(self, items):
2656 """Get passwords corresponding to each string in 'items',
2657 returning a dict. (The dict may have keys in addition to the
2658 values in 'items'.)
2659
2660 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2661 user edit that file to add more needed passwords. If no editor is
2662 available, or $ANDROID_PW_FILE isn't define, prompts the user
2663 interactively in the ordinary way.
2664 """
2665
2666 current = self.ReadFile()
2667
2668 first = True
2669 while True:
2670 missing = []
2671 for i in items:
2672 if i not in current or not current[i]:
2673 missing.append(i)
2674 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002675 if not missing:
2676 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002677
2678 for i in missing:
2679 current[i] = ""
2680
2681 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002682 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002683 if sys.version_info[0] >= 3:
2684 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002685 answer = raw_input("try to edit again? [y]> ").strip()
2686 if answer and answer[0] not in 'yY':
2687 raise RuntimeError("key passwords unavailable")
2688 first = False
2689
2690 current = self.UpdateAndReadFile(current)
2691
Kelvin Zhang0876c412020-06-23 15:06:58 -04002692 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002693 """Prompt the user to enter a value (password) for each key in
2694 'current' whose value is fales. Returns a new dict with all the
2695 values.
2696 """
2697 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002698 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002699 if v:
2700 result[k] = v
2701 else:
2702 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002703 result[k] = getpass.getpass(
2704 "Enter password for %s key> " % k).strip()
2705 if result[k]:
2706 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002707 return result
2708
2709 def UpdateAndReadFile(self, current):
2710 if not self.editor or not self.pwfile:
2711 return self.PromptResult(current)
2712
2713 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002714 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002715 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2716 f.write("# (Additional spaces are harmless.)\n\n")
2717
2718 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002719 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002720 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002721 f.write("[[[ %s ]]] %s\n" % (v, k))
2722 if not v and first_line is None:
2723 # position cursor on first line with no password.
2724 first_line = i + 4
2725 f.close()
2726
Tao Bao986ee862018-10-04 15:46:16 -07002727 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002728
2729 return self.ReadFile()
2730
2731 def ReadFile(self):
2732 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002733 if self.pwfile is None:
2734 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002735 try:
2736 f = open(self.pwfile, "r")
2737 for line in f:
2738 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002739 if not line or line[0] == '#':
2740 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002741 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2742 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002743 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002744 else:
2745 result[m.group(2)] = m.group(1)
2746 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002747 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002748 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002749 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002750 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002751
2752
Dan Albert8e0178d2015-01-27 15:53:15 -08002753def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2754 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002755
2756 # http://b/18015246
2757 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2758 # for files larger than 2GiB. We can work around this by adjusting their
2759 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2760 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2761 # it isn't clear to me exactly what circumstances cause this).
2762 # `zipfile.write()` must be used directly to work around this.
2763 #
2764 # This mess can be avoided if we port to python3.
2765 saved_zip64_limit = zipfile.ZIP64_LIMIT
2766 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2767
2768 if compress_type is None:
2769 compress_type = zip_file.compression
2770 if arcname is None:
2771 arcname = filename
2772
2773 saved_stat = os.stat(filename)
2774
2775 try:
2776 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2777 # file to be zipped and reset it when we're done.
2778 os.chmod(filename, perms)
2779
2780 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002781 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2782 # intentional. zip stores datetimes in local time without a time zone
2783 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2784 # in the zip archive.
2785 local_epoch = datetime.datetime.fromtimestamp(0)
2786 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002787 os.utime(filename, (timestamp, timestamp))
2788
2789 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2790 finally:
2791 os.chmod(filename, saved_stat.st_mode)
2792 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2793 zipfile.ZIP64_LIMIT = saved_zip64_limit
2794
2795
Tao Bao58c1b962015-05-20 09:32:18 -07002796def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002797 compress_type=None):
2798 """Wrap zipfile.writestr() function to work around the zip64 limit.
2799
2800 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2801 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2802 when calling crc32(bytes).
2803
2804 But it still works fine to write a shorter string into a large zip file.
2805 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2806 when we know the string won't be too long.
2807 """
2808
2809 saved_zip64_limit = zipfile.ZIP64_LIMIT
2810 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2811
2812 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2813 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002814 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002815 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002816 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002817 else:
Tao Baof3282b42015-04-01 11:21:55 -07002818 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002819 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2820 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2821 # such a case (since
2822 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2823 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2824 # permission bits. We follow the logic in Python 3 to get consistent
2825 # behavior between using the two versions.
2826 if not zinfo.external_attr:
2827 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002828
2829 # If compress_type is given, it overrides the value in zinfo.
2830 if compress_type is not None:
2831 zinfo.compress_type = compress_type
2832
Tao Bao58c1b962015-05-20 09:32:18 -07002833 # If perms is given, it has a priority.
2834 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002835 # If perms doesn't set the file type, mark it as a regular file.
2836 if perms & 0o770000 == 0:
2837 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002838 zinfo.external_attr = perms << 16
2839
Tao Baof3282b42015-04-01 11:21:55 -07002840 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002841 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2842
Dan Albert8b72aef2015-03-23 19:13:21 -07002843 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002844 zipfile.ZIP64_LIMIT = saved_zip64_limit
2845
2846
Tao Bao89d7ab22017-12-14 17:05:33 -08002847def ZipDelete(zip_filename, entries):
2848 """Deletes entries from a ZIP file.
2849
2850 Since deleting entries from a ZIP file is not supported, it shells out to
2851 'zip -d'.
2852
2853 Args:
2854 zip_filename: The name of the ZIP file.
2855 entries: The name of the entry, or the list of names to be deleted.
2856
2857 Raises:
2858 AssertionError: In case of non-zero return from 'zip'.
2859 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002860 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002861 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002862 # If list is empty, nothing to do
2863 if not entries:
2864 return
Tao Bao89d7ab22017-12-14 17:05:33 -08002865 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002866 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002867
2868
Tao Baof3282b42015-04-01 11:21:55 -07002869def ZipClose(zip_file):
2870 # http://b/18015246
2871 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2872 # central directory.
2873 saved_zip64_limit = zipfile.ZIP64_LIMIT
2874 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2875
2876 zip_file.close()
2877
2878 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002879
2880
2881class DeviceSpecificParams(object):
2882 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002883
Doug Zongker05d3dea2009-06-22 11:32:31 -07002884 def __init__(self, **kwargs):
2885 """Keyword arguments to the constructor become attributes of this
2886 object, which is passed to all functions in the device-specific
2887 module."""
Tao Bao38884282019-07-10 22:20:56 -07002888 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002889 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002890 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002891
2892 if self.module is None:
2893 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002894 if not path:
2895 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002896 try:
2897 if os.path.isdir(path):
2898 info = imp.find_module("releasetools", [path])
2899 else:
2900 d, f = os.path.split(path)
2901 b, x = os.path.splitext(f)
2902 if x == ".py":
2903 f = b
2904 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002905 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002906 self.module = imp.load_module("device_specific", *info)
2907 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002908 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002909
2910 def _DoCall(self, function_name, *args, **kwargs):
2911 """Call the named function in the device-specific module, passing
2912 the given args and kwargs. The first argument to the call will be
2913 the DeviceSpecific object itself. If there is no module, or the
2914 module does not define the function, return the value of the
2915 'default' kwarg (which itself defaults to None)."""
2916 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002917 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002918 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2919
2920 def FullOTA_Assertions(self):
2921 """Called after emitting the block of assertions at the top of a
2922 full OTA package. Implementations can add whatever additional
2923 assertions they like."""
2924 return self._DoCall("FullOTA_Assertions")
2925
Doug Zongkere5ff5902012-01-17 10:55:37 -08002926 def FullOTA_InstallBegin(self):
2927 """Called at the start of full OTA installation."""
2928 return self._DoCall("FullOTA_InstallBegin")
2929
Yifan Hong10c530d2018-12-27 17:34:18 -08002930 def FullOTA_GetBlockDifferences(self):
2931 """Called during full OTA installation and verification.
2932 Implementation should return a list of BlockDifference objects describing
2933 the update on each additional partitions.
2934 """
2935 return self._DoCall("FullOTA_GetBlockDifferences")
2936
Doug Zongker05d3dea2009-06-22 11:32:31 -07002937 def FullOTA_InstallEnd(self):
2938 """Called at the end of full OTA installation; typically this is
2939 used to install the image for the device's baseband processor."""
2940 return self._DoCall("FullOTA_InstallEnd")
2941
2942 def IncrementalOTA_Assertions(self):
2943 """Called after emitting the block of assertions at the top of an
2944 incremental OTA package. Implementations can add whatever
2945 additional assertions they like."""
2946 return self._DoCall("IncrementalOTA_Assertions")
2947
Doug Zongkere5ff5902012-01-17 10:55:37 -08002948 def IncrementalOTA_VerifyBegin(self):
2949 """Called at the start of the verification phase of incremental
2950 OTA installation; additional checks can be placed here to abort
2951 the script before any changes are made."""
2952 return self._DoCall("IncrementalOTA_VerifyBegin")
2953
Doug Zongker05d3dea2009-06-22 11:32:31 -07002954 def IncrementalOTA_VerifyEnd(self):
2955 """Called at the end of the verification phase of incremental OTA
2956 installation; additional checks can be placed here to abort the
2957 script before any changes are made."""
2958 return self._DoCall("IncrementalOTA_VerifyEnd")
2959
Doug Zongkere5ff5902012-01-17 10:55:37 -08002960 def IncrementalOTA_InstallBegin(self):
2961 """Called at the start of incremental OTA installation (after
2962 verification is complete)."""
2963 return self._DoCall("IncrementalOTA_InstallBegin")
2964
Yifan Hong10c530d2018-12-27 17:34:18 -08002965 def IncrementalOTA_GetBlockDifferences(self):
2966 """Called during incremental OTA installation and verification.
2967 Implementation should return a list of BlockDifference objects describing
2968 the update on each additional partitions.
2969 """
2970 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2971
Doug Zongker05d3dea2009-06-22 11:32:31 -07002972 def IncrementalOTA_InstallEnd(self):
2973 """Called at the end of incremental OTA installation; typically
2974 this is used to install the image for the device's baseband
2975 processor."""
2976 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002977
Tao Bao9bc6bb22015-11-09 16:58:28 -08002978 def VerifyOTA_Assertions(self):
2979 return self._DoCall("VerifyOTA_Assertions")
2980
Tao Bao76def242017-11-21 09:25:31 -08002981
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002982class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002983 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002984 self.name = name
2985 self.data = data
2986 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002987 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002988 self.sha1 = sha1(data).hexdigest()
2989
2990 @classmethod
2991 def FromLocalFile(cls, name, diskname):
2992 f = open(diskname, "rb")
2993 data = f.read()
2994 f.close()
2995 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002996
2997 def WriteToTemp(self):
2998 t = tempfile.NamedTemporaryFile()
2999 t.write(self.data)
3000 t.flush()
3001 return t
3002
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003003 def WriteToDir(self, d):
3004 with open(os.path.join(d, self.name), "wb") as fp:
3005 fp.write(self.data)
3006
Geremy Condra36bd3652014-02-06 19:45:10 -08003007 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003008 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003009
Tao Bao76def242017-11-21 09:25:31 -08003010
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003011DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003012 ".gz": "imgdiff",
3013 ".zip": ["imgdiff", "-z"],
3014 ".jar": ["imgdiff", "-z"],
3015 ".apk": ["imgdiff", "-z"],
3016 ".img": "imgdiff",
3017}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003018
Tao Bao76def242017-11-21 09:25:31 -08003019
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003020class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003021 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003022 self.tf = tf
3023 self.sf = sf
3024 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003025 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003026
3027 def ComputePatch(self):
3028 """Compute the patch (as a string of data) needed to turn sf into
3029 tf. Returns the same tuple as GetPatch()."""
3030
3031 tf = self.tf
3032 sf = self.sf
3033
Doug Zongker24cd2802012-08-14 16:36:15 -07003034 if self.diff_program:
3035 diff_program = self.diff_program
3036 else:
3037 ext = os.path.splitext(tf.name)[1]
3038 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003039
3040 ttemp = tf.WriteToTemp()
3041 stemp = sf.WriteToTemp()
3042
3043 ext = os.path.splitext(tf.name)[1]
3044
3045 try:
3046 ptemp = tempfile.NamedTemporaryFile()
3047 if isinstance(diff_program, list):
3048 cmd = copy.copy(diff_program)
3049 else:
3050 cmd = [diff_program]
3051 cmd.append(stemp.name)
3052 cmd.append(ttemp.name)
3053 cmd.append(ptemp.name)
3054 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003055 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003056
Doug Zongkerf8340082014-08-05 10:39:37 -07003057 def run():
3058 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003059 if e:
3060 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003061 th = threading.Thread(target=run)
3062 th.start()
3063 th.join(timeout=300) # 5 mins
3064 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003065 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003066 p.terminate()
3067 th.join(5)
3068 if th.is_alive():
3069 p.kill()
3070 th.join()
3071
Tianjie Xua2a9f992018-01-05 15:15:54 -08003072 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003073 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003074 self.patch = None
3075 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003076 diff = ptemp.read()
3077 finally:
3078 ptemp.close()
3079 stemp.close()
3080 ttemp.close()
3081
3082 self.patch = diff
3083 return self.tf, self.sf, self.patch
3084
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003085 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003086 """Returns a tuple of (target_file, source_file, patch_data).
3087
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003088 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003089 computing the patch failed.
3090 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003091 return self.tf, self.sf, self.patch
3092
3093
3094def ComputeDifferences(diffs):
3095 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003096 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003097
3098 # Do the largest files first, to try and reduce the long-pole effect.
3099 by_size = [(i.tf.size, i) for i in diffs]
3100 by_size.sort(reverse=True)
3101 by_size = [i[1] for i in by_size]
3102
3103 lock = threading.Lock()
3104 diff_iter = iter(by_size) # accessed under lock
3105
3106 def worker():
3107 try:
3108 lock.acquire()
3109 for d in diff_iter:
3110 lock.release()
3111 start = time.time()
3112 d.ComputePatch()
3113 dur = time.time() - start
3114 lock.acquire()
3115
3116 tf, sf, patch = d.GetPatch()
3117 if sf.name == tf.name:
3118 name = tf.name
3119 else:
3120 name = "%s (%s)" % (tf.name, sf.name)
3121 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003122 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003123 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003124 logger.info(
3125 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3126 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003127 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003128 except Exception:
3129 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003130 raise
3131
3132 # start worker threads; wait for them all to finish.
3133 threads = [threading.Thread(target=worker)
3134 for i in range(OPTIONS.worker_threads)]
3135 for th in threads:
3136 th.start()
3137 while threads:
3138 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003139
3140
Dan Albert8b72aef2015-03-23 19:13:21 -07003141class BlockDifference(object):
3142 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003143 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003144 self.tgt = tgt
3145 self.src = src
3146 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003147 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003148 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003149
Tao Baodd2a5892015-03-12 12:32:37 -07003150 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003151 version = max(
3152 int(i) for i in
3153 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003154 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003155 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003156
Tianjie Xu41976c72019-07-03 13:57:01 -07003157 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3158 version=self.version,
3159 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003160 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003161 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003162 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003163 self.touched_src_ranges = b.touched_src_ranges
3164 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003165
Yifan Hong10c530d2018-12-27 17:34:18 -08003166 # On devices with dynamic partitions, for new partitions,
3167 # src is None but OPTIONS.source_info_dict is not.
3168 if OPTIONS.source_info_dict is None:
3169 is_dynamic_build = OPTIONS.info_dict.get(
3170 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003171 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003172 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003173 is_dynamic_build = OPTIONS.source_info_dict.get(
3174 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003175 is_dynamic_source = partition in shlex.split(
3176 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003177
Yifan Hongbb2658d2019-01-25 12:30:58 -08003178 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003179 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3180
Yifan Hongbb2658d2019-01-25 12:30:58 -08003181 # For dynamic partitions builds, check partition list in both source
3182 # and target build because new partitions may be added, and existing
3183 # partitions may be removed.
3184 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3185
Yifan Hong10c530d2018-12-27 17:34:18 -08003186 if is_dynamic:
3187 self.device = 'map_partition("%s")' % partition
3188 else:
3189 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003190 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3191 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003192 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003193 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3194 OPTIONS.source_info_dict)
3195 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003196
Tao Baod8d14be2016-02-04 14:26:02 -08003197 @property
3198 def required_cache(self):
3199 return self._required_cache
3200
Tao Bao76def242017-11-21 09:25:31 -08003201 def WriteScript(self, script, output_zip, progress=None,
3202 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003203 if not self.src:
3204 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003205 script.Print("Patching %s image unconditionally..." % (self.partition,))
3206 else:
3207 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003208
Dan Albert8b72aef2015-03-23 19:13:21 -07003209 if progress:
3210 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003211 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003212
3213 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003214 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003215
Tao Bao9bc6bb22015-11-09 16:58:28 -08003216 def WriteStrictVerifyScript(self, script):
3217 """Verify all the blocks in the care_map, including clobbered blocks.
3218
3219 This differs from the WriteVerifyScript() function: a) it prints different
3220 error messages; b) it doesn't allow half-way updated images to pass the
3221 verification."""
3222
3223 partition = self.partition
3224 script.Print("Verifying %s..." % (partition,))
3225 ranges = self.tgt.care_map
3226 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003227 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003228 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3229 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003230 self.device, ranges_str,
3231 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003232 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003233 script.AppendExtra("")
3234
Tao Baod522bdc2016-04-12 15:53:16 -07003235 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003236 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003237
3238 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003239 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003240 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003241
3242 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003243 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003244 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003245 ranges = self.touched_src_ranges
3246 expected_sha1 = self.touched_src_sha1
3247 else:
3248 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3249 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003250
3251 # No blocks to be checked, skipping.
3252 if not ranges:
3253 return
3254
Tao Bao5ece99d2015-05-12 11:42:31 -07003255 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003256 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003257 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003258 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3259 '"%s.patch.dat")) then' % (
3260 self.device, ranges_str, expected_sha1,
3261 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003262 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003263 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003264
Tianjie Xufc3422a2015-12-15 11:53:59 -08003265 if self.version >= 4:
3266
3267 # Bug: 21124327
3268 # When generating incrementals for the system and vendor partitions in
3269 # version 4 or newer, explicitly check the first block (which contains
3270 # the superblock) of the partition to see if it's what we expect. If
3271 # this check fails, give an explicit log message about the partition
3272 # having been remounted R/W (the most likely explanation).
3273 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003274 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003275
3276 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003277 if partition == "system":
3278 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3279 else:
3280 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003281 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003282 'ifelse (block_image_recover({device}, "{ranges}") && '
3283 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003284 'package_extract_file("{partition}.transfer.list"), '
3285 '"{partition}.new.dat", "{partition}.patch.dat"), '
3286 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003287 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003288 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003289 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003290
Tao Baodd2a5892015-03-12 12:32:37 -07003291 # Abort the OTA update. Note that the incremental OTA cannot be applied
3292 # even if it may match the checksum of the target partition.
3293 # a) If version < 3, operations like move and erase will make changes
3294 # unconditionally and damage the partition.
3295 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003296 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003297 if partition == "system":
3298 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3299 else:
3300 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3301 script.AppendExtra((
3302 'abort("E%d: %s partition has unexpected contents");\n'
3303 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003304
Yifan Hong10c530d2018-12-27 17:34:18 -08003305 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003306 partition = self.partition
3307 script.Print('Verifying the updated %s image...' % (partition,))
3308 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3309 ranges = self.tgt.care_map
3310 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003311 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003312 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003313 self.device, ranges_str,
3314 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003315
3316 # Bug: 20881595
3317 # Verify that extended blocks are really zeroed out.
3318 if self.tgt.extended:
3319 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003320 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003321 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003322 self.device, ranges_str,
3323 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003324 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003325 if partition == "system":
3326 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3327 else:
3328 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003329 script.AppendExtra(
3330 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003331 ' abort("E%d: %s partition has unexpected non-zero contents after '
3332 'OTA update");\n'
3333 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003334 else:
3335 script.Print('Verified the updated %s image.' % (partition,))
3336
Tianjie Xu209db462016-05-24 17:34:52 -07003337 if partition == "system":
3338 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3339 else:
3340 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3341
Tao Bao5fcaaef2015-06-01 13:40:49 -07003342 script.AppendExtra(
3343 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003344 ' abort("E%d: %s partition has unexpected contents after OTA '
3345 'update");\n'
3346 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003347
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003348 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003349 ZipWrite(output_zip,
3350 '{}.transfer.list'.format(self.path),
3351 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003352
Tao Bao76def242017-11-21 09:25:31 -08003353 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3354 # its size. Quailty 9 almost triples the compression time but doesn't
3355 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003356 # zip | brotli(quality 6) | brotli(quality 9)
3357 # compressed_size: 942M | 869M (~8% reduced) | 854M
3358 # compression_time: 75s | 265s | 719s
3359 # decompression_time: 15s | 25s | 25s
3360
3361 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003362 brotli_cmd = ['brotli', '--quality=6',
3363 '--output={}.new.dat.br'.format(self.path),
3364 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003365 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003366 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003367
3368 new_data_name = '{}.new.dat.br'.format(self.partition)
3369 ZipWrite(output_zip,
3370 '{}.new.dat.br'.format(self.path),
3371 new_data_name,
3372 compress_type=zipfile.ZIP_STORED)
3373 else:
3374 new_data_name = '{}.new.dat'.format(self.partition)
3375 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3376
Dan Albert8e0178d2015-01-27 15:53:15 -08003377 ZipWrite(output_zip,
3378 '{}.patch.dat'.format(self.path),
3379 '{}.patch.dat'.format(self.partition),
3380 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003381
Tianjie Xu209db462016-05-24 17:34:52 -07003382 if self.partition == "system":
3383 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3384 else:
3385 code = ErrorCode.VENDOR_UPDATE_FAILURE
3386
Yifan Hong10c530d2018-12-27 17:34:18 -08003387 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003388 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003389 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003390 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003391 device=self.device, partition=self.partition,
3392 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003393 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003394
Kelvin Zhang0876c412020-06-23 15:06:58 -04003395 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003396 data = source.ReadRangeSet(ranges)
3397 ctx = sha1()
3398
3399 for p in data:
3400 ctx.update(p)
3401
3402 return ctx.hexdigest()
3403
Kelvin Zhang0876c412020-06-23 15:06:58 -04003404 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003405 """Return the hash value for all zero blocks."""
3406 zero_block = '\x00' * 4096
3407 ctx = sha1()
3408 for _ in range(num_blocks):
3409 ctx.update(zero_block)
3410
3411 return ctx.hexdigest()
3412
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003413
Tianjie Xu41976c72019-07-03 13:57:01 -07003414# Expose these two classes to support vendor-specific scripts
3415DataImage = images.DataImage
3416EmptyImage = images.EmptyImage
3417
Tao Bao76def242017-11-21 09:25:31 -08003418
Doug Zongker96a57e72010-09-26 14:57:41 -07003419# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003420PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003421 "ext4": "EMMC",
3422 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003423 "f2fs": "EMMC",
3424 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003425}
Doug Zongker96a57e72010-09-26 14:57:41 -07003426
Kelvin Zhang0876c412020-06-23 15:06:58 -04003427
Yifan Hongbdb32012020-05-07 12:38:53 -07003428def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3429 """
3430 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3431 backwards compatibility. It aborts if the fstab entry has slotselect option
3432 (unless check_no_slot is explicitly set to False).
3433 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003434 fstab = info["fstab"]
3435 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003436 if check_no_slot:
3437 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003438 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003439 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3440 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003441 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003442
3443
Yifan Hongbdb32012020-05-07 12:38:53 -07003444def GetTypeAndDeviceExpr(mount_point, info):
3445 """
3446 Return the filesystem of the partition, and an edify expression that evaluates
3447 to the device at runtime.
3448 """
3449 fstab = info["fstab"]
3450 if fstab:
3451 p = fstab[mount_point]
3452 device_expr = '"%s"' % fstab[mount_point].device
3453 if p.slotselect:
3454 device_expr = 'add_slot_suffix(%s)' % device_expr
3455 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003456 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003457
3458
3459def GetEntryForDevice(fstab, device):
3460 """
3461 Returns:
3462 The first entry in fstab whose device is the given value.
3463 """
3464 if not fstab:
3465 return None
3466 for mount_point in fstab:
3467 if fstab[mount_point].device == device:
3468 return fstab[mount_point]
3469 return None
3470
Kelvin Zhang0876c412020-06-23 15:06:58 -04003471
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003472def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003473 """Parses and converts a PEM-encoded certificate into DER-encoded.
3474
3475 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3476
3477 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003478 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003479 """
3480 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003481 save = False
3482 for line in data.split("\n"):
3483 if "--END CERTIFICATE--" in line:
3484 break
3485 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003486 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003487 if "--BEGIN CERTIFICATE--" in line:
3488 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003489 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003490 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003491
Tao Bao04e1f012018-02-04 12:13:35 -08003492
3493def ExtractPublicKey(cert):
3494 """Extracts the public key (PEM-encoded) from the given certificate file.
3495
3496 Args:
3497 cert: The certificate filename.
3498
3499 Returns:
3500 The public key string.
3501
3502 Raises:
3503 AssertionError: On non-zero return from 'openssl'.
3504 """
3505 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3506 # While openssl 1.1 writes the key into the given filename followed by '-out',
3507 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3508 # stdout instead.
3509 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3510 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3511 pubkey, stderrdata = proc.communicate()
3512 assert proc.returncode == 0, \
3513 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3514 return pubkey
3515
3516
Tao Bao1ac886e2019-06-26 11:58:22 -07003517def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003518 """Extracts the AVB public key from the given public or private key.
3519
3520 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003521 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003522 key: The input key file, which should be PEM-encoded public or private key.
3523
3524 Returns:
3525 The path to the extracted AVB public key file.
3526 """
3527 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3528 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003529 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003530 return output
3531
3532
Doug Zongker412c02f2014-02-13 10:58:24 -08003533def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3534 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003535 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003536
Tao Bao6d5d6232018-03-09 17:04:42 -08003537 Most of the space in the boot and recovery images is just the kernel, which is
3538 identical for the two, so the resulting patch should be efficient. Add it to
3539 the output zip, along with a shell script that is run from init.rc on first
3540 boot to actually do the patching and install the new recovery image.
3541
3542 Args:
3543 input_dir: The top-level input directory of the target-files.zip.
3544 output_sink: The callback function that writes the result.
3545 recovery_img: File object for the recovery image.
3546 boot_img: File objects for the boot image.
3547 info_dict: A dict returned by common.LoadInfoDict() on the input
3548 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003549 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003550 if info_dict is None:
3551 info_dict = OPTIONS.info_dict
3552
Tao Bao6d5d6232018-03-09 17:04:42 -08003553 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003554 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3555
3556 if board_uses_vendorimage:
3557 # In this case, the output sink is rooted at VENDOR
3558 recovery_img_path = "etc/recovery.img"
3559 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3560 sh_dir = "bin"
3561 else:
3562 # In this case the output sink is rooted at SYSTEM
3563 recovery_img_path = "vendor/etc/recovery.img"
3564 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3565 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003566
Tao Baof2cffbd2015-07-22 12:33:18 -07003567 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003568 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003569
3570 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003571 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003572 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003573 # With system-root-image, boot and recovery images will have mismatching
3574 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3575 # to handle such a case.
3576 if system_root_image:
3577 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003578 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003579 assert not os.path.exists(path)
3580 else:
3581 diff_program = ["imgdiff"]
3582 if os.path.exists(path):
3583 diff_program.append("-b")
3584 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003585 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003586 else:
3587 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003588
3589 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3590 _, _, patch = d.ComputePatch()
3591 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003592
Dan Albertebb19aa2015-03-27 19:11:53 -07003593 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003594 # The following GetTypeAndDevice()s need to use the path in the target
3595 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003596 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3597 check_no_slot=False)
3598 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3599 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003600 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003601 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003602
Tao Baof2cffbd2015-07-22 12:33:18 -07003603 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003604
3605 # Note that we use /vendor to refer to the recovery resources. This will
3606 # work for a separate vendor partition mounted at /vendor or a
3607 # /system/vendor subdirectory on the system partition, for which init will
3608 # create a symlink from /vendor to /system/vendor.
3609
3610 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003611if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3612 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003613 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003614 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3615 log -t recovery "Installing new recovery image: succeeded" || \\
3616 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003617else
3618 log -t recovery "Recovery image already installed"
3619fi
3620""" % {'type': recovery_type,
3621 'device': recovery_device,
3622 'sha1': recovery_img.sha1,
3623 'size': recovery_img.size}
3624 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003625 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003626if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3627 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003628 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003629 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3630 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3631 log -t recovery "Installing new recovery image: succeeded" || \\
3632 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003633else
3634 log -t recovery "Recovery image already installed"
3635fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003636""" % {'boot_size': boot_img.size,
3637 'boot_sha1': boot_img.sha1,
3638 'recovery_size': recovery_img.size,
3639 'recovery_sha1': recovery_img.sha1,
3640 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003641 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003642 'recovery_type': recovery_type,
3643 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003644 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003645
Bill Peckhame868aec2019-09-17 17:06:47 -07003646 # The install script location moved from /system/etc to /system/bin in the L
3647 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3648 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003649
Tao Bao32fcdab2018-10-12 10:30:39 -07003650 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003651
Tao Baoda30cfa2017-12-01 16:19:46 -08003652 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003653
3654
3655class DynamicPartitionUpdate(object):
3656 def __init__(self, src_group=None, tgt_group=None, progress=None,
3657 block_difference=None):
3658 self.src_group = src_group
3659 self.tgt_group = tgt_group
3660 self.progress = progress
3661 self.block_difference = block_difference
3662
3663 @property
3664 def src_size(self):
3665 if not self.block_difference:
3666 return 0
3667 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3668
3669 @property
3670 def tgt_size(self):
3671 if not self.block_difference:
3672 return 0
3673 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3674
3675 @staticmethod
3676 def _GetSparseImageSize(img):
3677 if not img:
3678 return 0
3679 return img.blocksize * img.total_blocks
3680
3681
3682class DynamicGroupUpdate(object):
3683 def __init__(self, src_size=None, tgt_size=None):
3684 # None: group does not exist. 0: no size limits.
3685 self.src_size = src_size
3686 self.tgt_size = tgt_size
3687
3688
3689class DynamicPartitionsDifference(object):
3690 def __init__(self, info_dict, block_diffs, progress_dict=None,
3691 source_info_dict=None):
3692 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003693 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003694
3695 self._remove_all_before_apply = False
3696 if source_info_dict is None:
3697 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003698 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003699
Tao Baof1113e92019-06-18 12:10:14 -07003700 block_diff_dict = collections.OrderedDict(
3701 [(e.partition, e) for e in block_diffs])
3702
Yifan Hong10c530d2018-12-27 17:34:18 -08003703 assert len(block_diff_dict) == len(block_diffs), \
3704 "Duplicated BlockDifference object for {}".format(
3705 [partition for partition, count in
3706 collections.Counter(e.partition for e in block_diffs).items()
3707 if count > 1])
3708
Yifan Hong79997e52019-01-23 16:56:19 -08003709 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003710
3711 for p, block_diff in block_diff_dict.items():
3712 self._partition_updates[p] = DynamicPartitionUpdate()
3713 self._partition_updates[p].block_difference = block_diff
3714
3715 for p, progress in progress_dict.items():
3716 if p in self._partition_updates:
3717 self._partition_updates[p].progress = progress
3718
3719 tgt_groups = shlex.split(info_dict.get(
3720 "super_partition_groups", "").strip())
3721 src_groups = shlex.split(source_info_dict.get(
3722 "super_partition_groups", "").strip())
3723
3724 for g in tgt_groups:
3725 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003726 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003727 assert p in self._partition_updates, \
3728 "{} is in target super_{}_partition_list but no BlockDifference " \
3729 "object is provided.".format(p, g)
3730 self._partition_updates[p].tgt_group = g
3731
3732 for g in src_groups:
3733 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003734 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003735 assert p in self._partition_updates, \
3736 "{} is in source super_{}_partition_list but no BlockDifference " \
3737 "object is provided.".format(p, g)
3738 self._partition_updates[p].src_group = g
3739
Yifan Hong45433e42019-01-18 13:55:25 -08003740 target_dynamic_partitions = set(shlex.split(info_dict.get(
3741 "dynamic_partition_list", "").strip()))
3742 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3743 if u.tgt_size)
3744 assert block_diffs_with_target == target_dynamic_partitions, \
3745 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3746 list(target_dynamic_partitions), list(block_diffs_with_target))
3747
3748 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3749 "dynamic_partition_list", "").strip()))
3750 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3751 if u.src_size)
3752 assert block_diffs_with_source == source_dynamic_partitions, \
3753 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3754 list(source_dynamic_partitions), list(block_diffs_with_source))
3755
Yifan Hong10c530d2018-12-27 17:34:18 -08003756 if self._partition_updates:
3757 logger.info("Updating dynamic partitions %s",
3758 self._partition_updates.keys())
3759
Yifan Hong79997e52019-01-23 16:56:19 -08003760 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003761
3762 for g in tgt_groups:
3763 self._group_updates[g] = DynamicGroupUpdate()
3764 self._group_updates[g].tgt_size = int(info_dict.get(
3765 "super_%s_group_size" % g, "0").strip())
3766
3767 for g in src_groups:
3768 if g not in self._group_updates:
3769 self._group_updates[g] = DynamicGroupUpdate()
3770 self._group_updates[g].src_size = int(source_info_dict.get(
3771 "super_%s_group_size" % g, "0").strip())
3772
3773 self._Compute()
3774
3775 def WriteScript(self, script, output_zip, write_verify_script=False):
3776 script.Comment('--- Start patching dynamic partitions ---')
3777 for p, u in self._partition_updates.items():
3778 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3779 script.Comment('Patch partition %s' % p)
3780 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3781 write_verify_script=False)
3782
3783 op_list_path = MakeTempFile()
3784 with open(op_list_path, 'w') as f:
3785 for line in self._op_list:
3786 f.write('{}\n'.format(line))
3787
3788 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3789
3790 script.Comment('Update dynamic partition metadata')
3791 script.AppendExtra('assert(update_dynamic_partitions('
3792 'package_extract_file("dynamic_partitions_op_list")));')
3793
3794 if write_verify_script:
3795 for p, u in self._partition_updates.items():
3796 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3797 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003798 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003799
3800 for p, u in self._partition_updates.items():
3801 if u.tgt_size and u.src_size <= u.tgt_size:
3802 script.Comment('Patch partition %s' % p)
3803 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3804 write_verify_script=write_verify_script)
3805 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003806 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003807
3808 script.Comment('--- End patching dynamic partitions ---')
3809
3810 def _Compute(self):
3811 self._op_list = list()
3812
3813 def append(line):
3814 self._op_list.append(line)
3815
3816 def comment(line):
3817 self._op_list.append("# %s" % line)
3818
3819 if self._remove_all_before_apply:
3820 comment('Remove all existing dynamic partitions and groups before '
3821 'applying full OTA')
3822 append('remove_all_groups')
3823
3824 for p, u in self._partition_updates.items():
3825 if u.src_group and not u.tgt_group:
3826 append('remove %s' % p)
3827
3828 for p, u in self._partition_updates.items():
3829 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3830 comment('Move partition %s from %s to default' % (p, u.src_group))
3831 append('move %s default' % p)
3832
3833 for p, u in self._partition_updates.items():
3834 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3835 comment('Shrink partition %s from %d to %d' %
3836 (p, u.src_size, u.tgt_size))
3837 append('resize %s %s' % (p, u.tgt_size))
3838
3839 for g, u in self._group_updates.items():
3840 if u.src_size is not None and u.tgt_size is None:
3841 append('remove_group %s' % g)
3842 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003843 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003844 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3845 append('resize_group %s %d' % (g, u.tgt_size))
3846
3847 for g, u in self._group_updates.items():
3848 if u.src_size is None and u.tgt_size is not None:
3849 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3850 append('add_group %s %d' % (g, u.tgt_size))
3851 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003852 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003853 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3854 append('resize_group %s %d' % (g, u.tgt_size))
3855
3856 for p, u in self._partition_updates.items():
3857 if u.tgt_group and not u.src_group:
3858 comment('Add partition %s to group %s' % (p, u.tgt_group))
3859 append('add %s %s' % (p, u.tgt_group))
3860
3861 for p, u in self._partition_updates.items():
3862 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003863 comment('Grow partition %s from %d to %d' %
3864 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003865 append('resize %s %d' % (p, u.tgt_size))
3866
3867 for p, u in self._partition_updates.items():
3868 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3869 comment('Move partition %s from default to %s' %
3870 (p, u.tgt_group))
3871 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003872
3873
jiajia tangf3f842b2021-03-17 21:49:44 +08003874def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003875 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003876 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003877
3878 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003879 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003880
3881 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003882 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003883 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003884 tmp_dir = MakeTempDir('boot_', suffix='.img')
3885 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003886 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3887 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003888 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3889 if not os.path.isfile(ramdisk):
3890 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3891 return None
3892 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003893 if ramdisk_format == RamdiskFormat.LZ4:
3894 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3895 elif ramdisk_format == RamdiskFormat.GZ:
3896 with open(ramdisk, 'rb') as input_stream:
3897 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003898 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3899 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003900 p2.wait()
3901 else:
3902 logger.error('Only support lz4 or minigzip ramdisk format.')
3903 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003904
3905 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3906 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3907 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3908 # the host environment.
3909 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003910 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003911
Yifan Hongc65a0542021-01-07 14:21:01 -08003912 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3913 prop_file = os.path.join(extracted_ramdisk, search_path)
3914 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003915 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003916 logger.warning(
3917 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003918
Yifan Hong7dc51172021-01-12 11:27:39 -08003919 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003920
Yifan Hong85ac5012021-01-07 14:43:46 -08003921 except ExternalError as e:
3922 logger.warning('Unable to get boot image build props: %s', e)
3923 return None
3924
3925
3926def GetBootImageTimestamp(boot_img):
3927 """
3928 Get timestamp from ramdisk within the boot image
3929
3930 Args:
3931 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3932
3933 Return:
3934 An integer that corresponds to the timestamp of the boot image, or None
3935 if file has unknown format. Raise exception if an unexpected error has
3936 occurred.
3937 """
3938 prop_file = GetBootImageBuildProp(boot_img)
3939 if not prop_file:
3940 return None
3941
3942 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3943 if props is None:
3944 return None
3945
3946 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003947 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3948 if timestamp:
3949 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003950 logger.warning(
3951 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003952 return None
3953
3954 except ExternalError as e:
3955 logger.warning('Unable to get boot image timestamp: %s', e)
3956 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003957
3958
3959def GetCareMap(which, imgname):
3960 """Returns the care_map string for the given partition.
3961
3962 Args:
3963 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3964 imgname: The filename of the image.
3965
3966 Returns:
3967 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3968 RangeSet; or None.
3969 """
3970 assert which in PARTITIONS_WITH_CARE_MAP
3971
3972 # which + "_image_size" contains the size that the actual filesystem image
3973 # resides in, which is all that needs to be verified. The additional blocks in
3974 # the image file contain verity metadata, by reading which would trigger
3975 # invalid reads.
3976 image_size = OPTIONS.info_dict.get(which + "_image_size")
3977 if not image_size:
3978 return None
3979
David Anderson9e95a022021-08-31 21:32:45 -07003980 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
3981
Kelvin Zhang27324132021-03-22 15:38:38 -04003982 image_blocks = int(image_size) // 4096 - 1
Kelvin Zhang98ef7bb2022-01-07 14:41:46 -08003983 # It's OK for image_blocks to be 0, because care map ranges are inclusive.
3984 # So 0-0 means "just block 0", which is valid.
3985 assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
3986 which, image_size)
Kelvin Zhang27324132021-03-22 15:38:38 -04003987
3988 # For sparse images, we will only check the blocks that are listed in the care
3989 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07003990 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04003991 simg = sparse_img.SparseImage(imgname)
3992 care_map_ranges = simg.care_map.intersect(
3993 rangelib.RangeSet("0-{}".format(image_blocks)))
3994
3995 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3996 # image.
3997 else:
3998 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3999
4000 return [which, care_map_ranges.to_string_raw()]
4001
4002
4003def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
4004 """Generates and adds care_map.pb for a/b partition that has care_map.
4005
4006 Args:
4007 output_file: The output zip file (needs to be already open),
4008 or file path to write care_map.pb.
4009 ab_partitions: The list of A/B partitions.
4010 image_paths: A map from the partition name to the image path.
4011 """
4012 if not output_file:
4013 raise ExternalError('Expected output_file for AddCareMapForAbOta')
4014
4015 care_map_list = []
4016 for partition in ab_partitions:
4017 partition = partition.strip()
4018 if partition not in PARTITIONS_WITH_CARE_MAP:
4019 continue
4020
4021 verity_block_device = "{}_verity_block_device".format(partition)
4022 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
4023 if (verity_block_device in OPTIONS.info_dict or
4024 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
4025 if partition not in image_paths:
4026 logger.warning('Potential partition with care_map missing from images: %s',
4027 partition)
4028 continue
4029 image_path = image_paths[partition]
4030 if not os.path.exists(image_path):
4031 raise ExternalError('Expected image at path {}'.format(image_path))
4032
4033 care_map = GetCareMap(partition, image_path)
4034 if not care_map:
4035 continue
4036 care_map_list += care_map
4037
4038 # adds fingerprint field to the care_map
4039 # TODO(xunchang) revisit the fingerprint calculation for care_map.
4040 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
4041 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
4042 "ro.{}.build.thumbprint".format(partition)]
4043
4044 present_props = [x for x in prop_name_list if
4045 partition_props and partition_props.GetProp(x)]
4046 if not present_props:
4047 logger.warning(
4048 "fingerprint is not present for partition %s", partition)
4049 property_id, fingerprint = "unknown", "unknown"
4050 else:
4051 property_id = present_props[0]
4052 fingerprint = partition_props.GetProp(property_id)
4053 care_map_list += [property_id, fingerprint]
4054
4055 if not care_map_list:
4056 return
4057
4058 # Converts the list into proto buf message by calling care_map_generator; and
4059 # writes the result to a temp file.
4060 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
4061 suffix=".txt")
4062 with open(temp_care_map_text, 'w') as text_file:
4063 text_file.write('\n'.join(care_map_list))
4064
4065 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
4066 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
4067 RunAndCheckOutput(care_map_gen_cmd)
4068
4069 if not isinstance(output_file, zipfile.ZipFile):
4070 shutil.copy(temp_care_map, output_file)
4071 return
4072 # output_file is a zip file
4073 care_map_path = "META/care_map.pb"
4074 if care_map_path in output_file.namelist():
4075 # Copy the temp file into the OPTIONS.input_tmp dir and update the
4076 # replace_updated_files_list used by add_img_to_target_files
4077 if not OPTIONS.replace_updated_files_list:
4078 OPTIONS.replace_updated_files_list = []
4079 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
4080 OPTIONS.replace_updated_files_list.append(care_map_path)
4081 else:
4082 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07004083
4084
4085def IsSparseImage(filepath):
4086 with open(filepath, 'rb') as fp:
4087 # Magic for android sparse image format
4088 # https://source.android.com/devices/bootloader/images
4089 return fp.read(4) == b'\x3A\xFF\x26\xED'