blob: 701b276914204f84c112870e10e84fa557ca27c0 [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 = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800112# descriptor into vbmeta.img. When adding a new entry here, the
113# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
114# accordingly.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000115AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Lucas Wei03230252022-04-18 16:00:40 +0800116 'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000117 'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800118
Tao Bao08c190f2019-06-03 23:07:58 -0700119# Chained VBMeta partitions.
120AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
121
Tianjie Xu861f4132018-09-12 11:49:33 -0700122# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400123PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700124 'system',
125 'vendor',
126 'product',
127 'system_ext',
128 'odm',
129 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700130 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000131 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhang563750f2021-04-28 12:46:17 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhang563750f2021-04-28 12:46:17 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhang563750f2021-04-28 12:46:17 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Kelvin Zhang766eea72021-06-03 09:36:08 -0400279 if verbose is None:
280 verbose = OPTIONS.verbose
281
Tao Bao32fcdab2018-10-12 10:30:39 -0700282 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400283 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700285 return subprocess.Popen(args, **kwargs)
286
287
Tao Bao986ee862018-10-04 15:46:16 -0700288def RunAndCheckOutput(args, verbose=None, **kwargs):
289 """Runs the given command and returns the output.
290
291 Args:
292 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700293 verbose: Whether the commands should be shown. Default to the global
294 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700295 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
296 stdin, etc. stdout and stderr will default to subprocess.PIPE and
297 subprocess.STDOUT respectively unless caller specifies any of them.
298
299 Returns:
300 The output string.
301
302 Raises:
303 ExternalError: On non-zero exit from the command.
304 """
Tao Bao986ee862018-10-04 15:46:16 -0700305 proc = Run(args, verbose=verbose, **kwargs)
306 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800307 if output is None:
308 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700309 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400310 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700311 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700312 if proc.returncode != 0:
313 raise ExternalError(
314 "Failed to run command '{}' (exit code {}):\n{}".format(
315 args, proc.returncode, output))
316 return output
317
318
Tao Baoc765cca2018-01-31 17:32:40 -0800319def RoundUpTo4K(value):
320 rounded_up = value + 4095
321 return rounded_up - (rounded_up % 4096)
322
323
Ying Wang7e6d4e42010-12-13 16:25:36 -0800324def CloseInheritedPipes():
325 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
326 before doing other work."""
327 if platform.system() != "Darwin":
328 return
329 for d in range(3, 1025):
330 try:
331 stat = os.fstat(d)
332 if stat is not None:
333 pipebit = stat[0] & 0x1000
334 if pipebit != 0:
335 os.close(d)
336 except OSError:
337 pass
338
339
Tao Bao1c320f82019-10-04 23:25:12 -0700340class BuildInfo(object):
341 """A class that holds the information for a given build.
342
343 This class wraps up the property querying for a given source or target build.
344 It abstracts away the logic of handling OEM-specific properties, and caches
345 the commonly used properties such as fingerprint.
346
347 There are two types of info dicts: a) build-time info dict, which is generated
348 at build time (i.e. included in a target_files zip); b) OEM info dict that is
349 specified at package generation time (via command line argument
350 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
351 having "oem_fingerprint_properties" in build-time info dict), all the queries
352 would be answered based on build-time info dict only. Otherwise if using
353 OEM-specific properties, some of them will be calculated from two info dicts.
354
355 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800356 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700357
358 Attributes:
359 info_dict: The build-time info dict.
360 is_ab: Whether it's a build that uses A/B OTA.
361 oem_dicts: A list of OEM dicts.
362 oem_props: A list of OEM properties that should be read from OEM dicts; None
363 if the build doesn't use any OEM-specific property.
364 fingerprint: The fingerprint of the build, which would be calculated based
365 on OEM properties if applicable.
366 device: The device name, which could come from OEM dicts if applicable.
367 """
368
369 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
370 "ro.product.manufacturer", "ro.product.model",
371 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700372 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
373 "product", "odm", "vendor", "system_ext", "system"]
374 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
375 "product", "product_services", "odm", "vendor", "system"]
376 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700377
Tianjiefdda51d2021-05-05 14:46:35 -0700378 # The length of vbmeta digest to append to the fingerprint
379 _VBMETA_DIGEST_SIZE_USED = 8
380
381 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700382 """Initializes a BuildInfo instance with the given dicts.
383
384 Note that it only wraps up the given dicts, without making copies.
385
386 Arguments:
387 info_dict: The build-time info dict.
388 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
389 that it always uses the first dict to calculate the fingerprint or the
390 device name. The rest would be used for asserting OEM properties only
391 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700392 use_legacy_id: Use the legacy build id to construct the fingerprint. This
393 is used when we need a BuildInfo class, while the vbmeta digest is
394 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700395
396 Raises:
397 ValueError: On invalid inputs.
398 """
399 self.info_dict = info_dict
400 self.oem_dicts = oem_dicts
401
402 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700403 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700404
Hongguang Chend7c160f2020-05-03 21:24:26 -0700405 # Skip _oem_props if oem_dicts is None to use BuildInfo in
406 # sign_target_files_apks
407 if self.oem_dicts:
408 self._oem_props = info_dict.get("oem_fingerprint_properties")
409 else:
410 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700411
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 def check_fingerprint(fingerprint):
413 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
414 raise ValueError(
415 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
416 "3.2.2. Build Parameters.".format(fingerprint))
417
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800419 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800420 try:
421 fingerprint = self.CalculatePartitionFingerprint(partition)
422 check_fingerprint(fingerprint)
423 self._partition_fingerprints[partition] = fingerprint
424 except ExternalError:
425 continue
426 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800427 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800428 # need a fingerprint when creating the image.
429 self._partition_fingerprints[
430 "system_other"] = self._partition_fingerprints["system"]
431
Tao Bao1c320f82019-10-04 23:25:12 -0700432 # These two should be computed only after setting self._oem_props.
433 self._device = self.GetOemProperty("ro.product.device")
434 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700436
437 @property
438 def is_ab(self):
439 return self._is_ab
440
441 @property
442 def device(self):
443 return self._device
444
445 @property
446 def fingerprint(self):
447 return self._fingerprint
448
449 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400450 def is_vabc(self):
451 vendor_prop = self.info_dict.get("vendor.build.prop")
452 vabc_enabled = vendor_prop and \
453 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
454 return vabc_enabled
455
456 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700457 def is_android_r(self):
458 system_prop = self.info_dict.get("system.build.prop")
459 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
460
461 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700462 def is_vabc_xor(self):
463 vendor_prop = self.info_dict.get("vendor.build.prop")
464 vabc_xor_enabled = vendor_prop and \
465 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
466 return vabc_xor_enabled
467
468 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400469 def vendor_suppressed_vabc(self):
470 vendor_prop = self.info_dict.get("vendor.build.prop")
471 vabc_suppressed = vendor_prop and \
472 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
473 return vabc_suppressed and vabc_suppressed.lower() == "true"
474
475 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def oem_props(self):
477 return self._oem_props
478
479 def __getitem__(self, key):
480 return self.info_dict[key]
481
482 def __setitem__(self, key, value):
483 self.info_dict[key] = value
484
485 def get(self, key, default=None):
486 return self.info_dict.get(key, default)
487
488 def items(self):
489 return self.info_dict.items()
490
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 def _GetRawBuildProp(self, prop, partition):
492 prop_file = '{}.build.prop'.format(
493 partition) if partition else 'build.prop'
494 partition_props = self.info_dict.get(prop_file)
495 if not partition_props:
496 return None
497 return partition_props.GetProp(prop)
498
Daniel Normand5fe8622020-01-08 17:01:11 -0800499 def GetPartitionBuildProp(self, prop, partition):
500 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800501
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000502 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000503 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000504 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800505
Daniel Normand5fe8622020-01-08 17:01:11 -0800506 # If provided a partition for this property, only look within that
507 # partition's build.prop.
508 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800509 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800510 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800511 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512
513 prop_val = self._GetRawBuildProp(prop, partition)
514 if prop_val is not None:
515 return prop_val
516 raise ExternalError("couldn't find %s in %s.build.prop" %
517 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800518
Tao Bao1c320f82019-10-04 23:25:12 -0700519 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700521 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
522 return self._ResolveRoProductBuildProp(prop)
523
Tianjiefdda51d2021-05-05 14:46:35 -0700524 if prop == "ro.build.id":
525 return self._GetBuildId()
526
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000527 prop_val = self._GetRawBuildProp(prop, None)
528 if prop_val is not None:
529 return prop_val
530
531 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700532
533 def _ResolveRoProductBuildProp(self, prop):
534 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000535 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700536 if prop_val:
537 return prop_val
538
Steven Laver8e2086e2020-04-27 16:26:31 -0700539 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000540 source_order_val = self._GetRawBuildProp(
541 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700542 if source_order_val:
543 source_order = source_order_val.split(",")
544 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700545 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700546
547 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700548 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700549 raise ExternalError(
550 "Invalid ro.product.property_source_order '{}'".format(source_order))
551
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000552 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700553 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000554 "ro.product", "ro.product.{}".format(source_partition), 1)
555 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700556 if prop_val:
557 return prop_val
558
559 raise ExternalError("couldn't resolve {}".format(prop))
560
Steven Laver8e2086e2020-04-27 16:26:31 -0700561 def _GetRoProductPropsDefaultSourceOrder(self):
562 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
563 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000564 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700565 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000566 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700567 if android_version == "10":
568 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
569 # NOTE: float() conversion of android_version will have rounding error.
570 # We are checking for "9" or less, and using "< 10" is well outside of
571 # possible floating point rounding.
572 try:
573 android_version_val = float(android_version)
574 except ValueError:
575 android_version_val = 0
576 if android_version_val < 10:
577 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
578 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
579
Tianjieb37c5be2020-10-15 21:27:10 -0700580 def _GetPlatformVersion(self):
581 version_sdk = self.GetBuildProp("ro.build.version.sdk")
582 # init code switches to version_release_or_codename (see b/158483506). After
583 # API finalization, release_or_codename will be the same as release. This
584 # is the best effort to support pre-S dev stage builds.
585 if int(version_sdk) >= 30:
586 try:
587 return self.GetBuildProp("ro.build.version.release_or_codename")
588 except ExternalError:
589 logger.warning('Failed to find ro.build.version.release_or_codename')
590
591 return self.GetBuildProp("ro.build.version.release")
592
Tianjiefdda51d2021-05-05 14:46:35 -0700593 def _GetBuildId(self):
594 build_id = self._GetRawBuildProp("ro.build.id", None)
595 if build_id:
596 return build_id
597
598 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
599 if not legacy_build_id:
600 raise ExternalError("Couldn't find build id in property file")
601
602 if self.use_legacy_id:
603 return legacy_build_id
604
605 # Append the top 8 chars of vbmeta digest to the existing build id. The
606 # logic needs to match the one in init, so that OTA can deliver correctly.
607 avb_enable = self.info_dict.get("avb_enable") == "true"
608 if not avb_enable:
609 raise ExternalError("AVB isn't enabled when using legacy build id")
610
611 vbmeta_digest = self.info_dict.get("vbmeta_digest")
612 if not vbmeta_digest:
613 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
614 " id")
615 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
616 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
617
618 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
619 return legacy_build_id + '.' + digest_prefix
620
Tianjieb37c5be2020-10-15 21:27:10 -0700621 def _GetPartitionPlatformVersion(self, partition):
622 try:
623 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
624 partition)
625 except ExternalError:
626 return self.GetPartitionBuildProp("ro.build.version.release",
627 partition)
628
Tao Bao1c320f82019-10-04 23:25:12 -0700629 def GetOemProperty(self, key):
630 if self.oem_props is not None and key in self.oem_props:
631 return self.oem_dicts[0][key]
632 return self.GetBuildProp(key)
633
Daniel Normand5fe8622020-01-08 17:01:11 -0800634 def GetPartitionFingerprint(self, partition):
635 return self._partition_fingerprints.get(partition, None)
636
637 def CalculatePartitionFingerprint(self, partition):
638 try:
639 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
640 except ExternalError:
641 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
642 self.GetPartitionBuildProp("ro.product.brand", partition),
643 self.GetPartitionBuildProp("ro.product.name", partition),
644 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700645 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800646 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400647 self.GetPartitionBuildProp(
648 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800649 self.GetPartitionBuildProp("ro.build.type", partition),
650 self.GetPartitionBuildProp("ro.build.tags", partition))
651
Tao Bao1c320f82019-10-04 23:25:12 -0700652 def CalculateFingerprint(self):
653 if self.oem_props is None:
654 try:
655 return self.GetBuildProp("ro.build.fingerprint")
656 except ExternalError:
657 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
658 self.GetBuildProp("ro.product.brand"),
659 self.GetBuildProp("ro.product.name"),
660 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700661 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700662 self.GetBuildProp("ro.build.id"),
663 self.GetBuildProp("ro.build.version.incremental"),
664 self.GetBuildProp("ro.build.type"),
665 self.GetBuildProp("ro.build.tags"))
666 return "%s/%s/%s:%s" % (
667 self.GetOemProperty("ro.product.brand"),
668 self.GetOemProperty("ro.product.name"),
669 self.GetOemProperty("ro.product.device"),
670 self.GetBuildProp("ro.build.thumbprint"))
671
672 def WriteMountOemScript(self, script):
673 assert self.oem_props is not None
674 recovery_mount_options = self.info_dict.get("recovery_mount_options")
675 script.Mount("/oem", recovery_mount_options)
676
677 def WriteDeviceAssertions(self, script, oem_no_mount):
678 # Read the property directly if not using OEM properties.
679 if not self.oem_props:
680 script.AssertDevice(self.device)
681 return
682
683 # Otherwise assert OEM properties.
684 if not self.oem_dicts:
685 raise ExternalError(
686 "No OEM file provided to answer expected assertions")
687
688 for prop in self.oem_props.split():
689 values = []
690 for oem_dict in self.oem_dicts:
691 if prop in oem_dict:
692 values.append(oem_dict[prop])
693 if not values:
694 raise ExternalError(
695 "The OEM file is missing the property %s" % (prop,))
696 script.AssertOemProperty(prop, values, oem_no_mount)
697
698
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000699def ReadFromInputFile(input_file, fn):
700 """Reads the contents of fn from input zipfile or directory."""
701 if isinstance(input_file, zipfile.ZipFile):
702 return input_file.read(fn).decode()
703 else:
704 path = os.path.join(input_file, *fn.split("/"))
705 try:
706 with open(path) as f:
707 return f.read()
708 except IOError as e:
709 if e.errno == errno.ENOENT:
710 raise KeyError(fn)
711
712
Yifan Hong10482a22021-01-07 14:38:41 -0800713def ExtractFromInputFile(input_file, fn):
714 """Extracts the contents of fn from input zipfile or directory into a file."""
715 if isinstance(input_file, zipfile.ZipFile):
716 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500717 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800718 f.write(input_file.read(fn))
719 return tmp_file
720 else:
721 file = os.path.join(input_file, *fn.split("/"))
722 if not os.path.exists(file):
723 raise KeyError(fn)
724 return file
725
Kelvin Zhang563750f2021-04-28 12:46:17 -0400726
jiajia tangf3f842b2021-03-17 21:49:44 +0800727class RamdiskFormat(object):
728 LZ4 = 1
729 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800730
Kelvin Zhang563750f2021-04-28 12:46:17 -0400731
TJ Rhoades6f488e92022-05-01 22:16:22 -0700732def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800733 if info_dict.get('lz4_ramdisks') == 'true':
734 ramdisk_format = RamdiskFormat.LZ4
735 else:
736 ramdisk_format = RamdiskFormat.GZ
737 return ramdisk_format
738
Kelvin Zhang563750f2021-04-28 12:46:17 -0400739
Tao Bao410ad8b2018-08-24 12:08:38 -0700740def LoadInfoDict(input_file, repacking=False):
741 """Loads the key/value pairs from the given input target_files.
742
Tianjiea85bdf02020-07-29 11:56:19 -0700743 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700744 checks and returns the parsed key/value pairs for to the given build. It's
745 usually called early when working on input target_files files, e.g. when
746 generating OTAs, or signing builds. Note that the function may be called
747 against an old target_files file (i.e. from past dessert releases). So the
748 property parsing needs to be backward compatible.
749
750 In a `META/misc_info.txt`, a few properties are stored as links to the files
751 in the PRODUCT_OUT directory. It works fine with the build system. However,
752 they are no longer available when (re)generating images from target_files zip.
753 When `repacking` is True, redirect these properties to the actual files in the
754 unzipped directory.
755
756 Args:
757 input_file: The input target_files file, which could be an open
758 zipfile.ZipFile instance, or a str for the dir that contains the files
759 unzipped from a target_files file.
760 repacking: Whether it's trying repack an target_files file after loading the
761 info dict (default: False). If so, it will rewrite a few loaded
762 properties (e.g. selinux_fc, root_dir) to point to the actual files in
763 target_files file. When doing repacking, `input_file` must be a dir.
764
765 Returns:
766 A dict that contains the parsed key/value pairs.
767
768 Raises:
769 AssertionError: On invalid input arguments.
770 ValueError: On malformed input values.
771 """
772 if repacking:
773 assert isinstance(input_file, str), \
774 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700775
Doug Zongkerc9253822014-02-04 12:17:58 -0800776 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000777 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800778
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700779 try:
Michael Runge6e836112014-04-15 17:40:21 -0700780 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700781 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700782 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700783
Tao Bao410ad8b2018-08-24 12:08:38 -0700784 if "recovery_api_version" not in d:
785 raise ValueError("Failed to find 'recovery_api_version'")
786 if "fstab_version" not in d:
787 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800788
Tao Bao410ad8b2018-08-24 12:08:38 -0700789 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700790 # "selinux_fc" properties should point to the file_contexts files
791 # (file_contexts.bin) under META/.
792 for key in d:
793 if key.endswith("selinux_fc"):
794 fc_basename = os.path.basename(d[key])
795 fc_config = os.path.join(input_file, "META", fc_basename)
796 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700797
Daniel Norman72c626f2019-05-13 15:58:14 -0700798 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700799
Tom Cherryd14b8952018-08-09 14:26:00 -0700800 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700801 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700802 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700803 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700804
David Anderson0ec64ac2019-12-06 12:21:18 -0800805 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700806 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000807 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800808 key_name = part_name + "_base_fs_file"
809 if key_name not in d:
810 continue
811 basename = os.path.basename(d[key_name])
812 base_fs_file = os.path.join(input_file, "META", basename)
813 if os.path.exists(base_fs_file):
814 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700815 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700816 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800817 "Failed to find %s base fs file: %s", part_name, base_fs_file)
818 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700819
Doug Zongker37974732010-09-16 17:44:38 -0700820 def makeint(key):
821 if key in d:
822 d[key] = int(d[key], 0)
823
824 makeint("recovery_api_version")
825 makeint("blocksize")
826 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700827 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700828 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700829 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700830 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800831 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700832
Steve Muckle903a1ca2020-05-07 17:32:10 -0700833 boot_images = "boot.img"
834 if "boot_images" in d:
835 boot_images = d["boot_images"]
836 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400837 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700838
Tao Bao765668f2019-10-04 22:03:00 -0700839 # Load recovery fstab if applicable.
840 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700841 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800842
Tianjie Xu861f4132018-09-12 11:49:33 -0700843 # Tries to load the build props for all partitions with care_map, including
844 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800845 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800846 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000847 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800848 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700849 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800850
Tao Bao3ed35d32019-10-07 20:48:48 -0700851 # Set up the salt (based on fingerprint) that will be used when adding AVB
852 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800853 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700854 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800855 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800856 fingerprint = build_info.GetPartitionFingerprint(partition)
857 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400858 d["avb_{}_salt".format(partition)] = sha256(
859 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700860
861 # Set the vbmeta digest if exists
862 try:
863 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
864 except KeyError:
865 pass
866
Kelvin Zhang39aea442020-08-17 11:04:25 -0400867 try:
868 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
869 except KeyError:
870 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700871 return d
872
Tao Baod1de6f32017-03-01 16:38:48 -0800873
Daniel Norman4cc9df62019-07-18 10:11:07 -0700874def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900875 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700876 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900877
Daniel Norman4cc9df62019-07-18 10:11:07 -0700878
879def LoadDictionaryFromFile(file_path):
880 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900881 return LoadDictionaryFromLines(lines)
882
883
Michael Runge6e836112014-04-15 17:40:21 -0700884def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700885 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700886 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700887 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700888 if not line or line.startswith("#"):
889 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700890 if "=" in line:
891 name, value = line.split("=", 1)
892 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700893 return d
894
Tao Baod1de6f32017-03-01 16:38:48 -0800895
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000896class PartitionBuildProps(object):
897 """The class holds the build prop of a particular partition.
898
899 This class loads the build.prop and holds the build properties for a given
900 partition. It also partially recognizes the 'import' statement in the
901 build.prop; and calculates alternative values of some specific build
902 properties during runtime.
903
904 Attributes:
905 input_file: a zipped target-file or an unzipped target-file directory.
906 partition: name of the partition.
907 props_allow_override: a list of build properties to search for the
908 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000909 build_props: a dict of build properties for the given partition.
910 prop_overrides: a set of props that are overridden by import.
911 placeholder_values: A dict of runtime variables' values to replace the
912 placeholders in the build.prop file. We expect exactly one value for
913 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800914 ramdisk_format: If name is "boot", the format of ramdisk inside the
915 boot image. Otherwise, its value is ignored.
916 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000917 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400918
Tianjie Xu9afb2212020-05-10 21:48:15 +0000919 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000920 self.input_file = input_file
921 self.partition = name
922 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000923 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000924 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000925 self.prop_overrides = set()
926 self.placeholder_values = {}
927 if placeholder_values:
928 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000929
930 @staticmethod
931 def FromDictionary(name, build_props):
932 """Constructs an instance from a build prop dictionary."""
933
934 props = PartitionBuildProps("unknown", name)
935 props.build_props = build_props.copy()
936 return props
937
938 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800939 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000940 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800941
Devin Mooreafdd7c72021-12-13 22:04:08 +0000942 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400943 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000944 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800945 else:
946 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
947
948 props = PartitionBuildProps(input_file, name, placeholder_values)
949 props._LoadBuildProp(data)
950 return props
951
952 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000953 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800954 """
955 Read build.prop for boot image from input_file.
956 Return empty string if not found.
957 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000958 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800959 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000960 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800961 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000962 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800963 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800964 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800965 if prop_file is None:
966 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500967 with open(prop_file, "r") as f:
968 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800969
970 @staticmethod
971 def _ReadPartitionPropFile(input_file, name):
972 """
973 Read build.prop for name from input_file.
974 Return empty string if not found.
975 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000976 data = ''
977 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
978 '{}/build.prop'.format(name.upper())]:
979 try:
980 data = ReadFromInputFile(input_file, prop_file)
981 break
982 except KeyError:
983 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800984 if data == '':
985 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800986 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000987
Yifan Hong125d0b62020-09-24 17:07:03 -0700988 @staticmethod
989 def FromBuildPropFile(name, build_prop_file):
990 """Constructs an instance from a build prop file."""
991
992 props = PartitionBuildProps("unknown", name)
993 with open(build_prop_file) as f:
994 props._LoadBuildProp(f.read())
995 return props
996
Tianjie Xu9afb2212020-05-10 21:48:15 +0000997 def _LoadBuildProp(self, data):
998 for line in data.split('\n'):
999 line = line.strip()
1000 if not line or line.startswith("#"):
1001 continue
1002 if line.startswith("import"):
1003 overrides = self._ImportParser(line)
1004 duplicates = self.prop_overrides.intersection(overrides.keys())
1005 if duplicates:
1006 raise ValueError('prop {} is overridden multiple times'.format(
1007 ','.join(duplicates)))
1008 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1009 self.build_props.update(overrides)
1010 elif "=" in line:
1011 name, value = line.split("=", 1)
1012 if name in self.prop_overrides:
1013 raise ValueError('prop {} is set again after overridden by import '
1014 'statement'.format(name))
1015 self.build_props[name] = value
1016
1017 def _ImportParser(self, line):
1018 """Parses the build prop in a given import statement."""
1019
1020 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001021 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001022 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001023
1024 if len(tokens) == 3:
1025 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1026 return {}
1027
Tianjie Xu9afb2212020-05-10 21:48:15 +00001028 import_path = tokens[1]
1029 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001030 logger.warn('Unrecognized import path {}'.format(line))
1031 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001032
1033 # We only recognize a subset of import statement that the init process
1034 # supports. And we can loose the restriction based on how the dynamic
1035 # fingerprint is used in practice. The placeholder format should be
1036 # ${placeholder}, and its value should be provided by the caller through
1037 # the placeholder_values.
1038 for prop, value in self.placeholder_values.items():
1039 prop_place_holder = '${{{}}}'.format(prop)
1040 if prop_place_holder in import_path:
1041 import_path = import_path.replace(prop_place_holder, value)
1042 if '$' in import_path:
1043 logger.info('Unresolved place holder in import path %s', import_path)
1044 return {}
1045
1046 import_path = import_path.replace('/{}'.format(self.partition),
1047 self.partition.upper())
1048 logger.info('Parsing build props override from %s', import_path)
1049
1050 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1051 d = LoadDictionaryFromLines(lines)
1052 return {key: val for key, val in d.items()
1053 if key in self.props_allow_override}
1054
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001055 def GetProp(self, prop):
1056 return self.build_props.get(prop)
1057
1058
Tianjie Xucfa86222016-03-07 16:31:19 -08001059def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1060 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001061 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001062 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001063 self.mount_point = mount_point
1064 self.fs_type = fs_type
1065 self.device = device
1066 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001067 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001068 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001069
1070 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001071 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001072 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001073 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001074 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001075
Tao Baod1de6f32017-03-01 16:38:48 -08001076 assert fstab_version == 2
1077
1078 d = {}
1079 for line in data.split("\n"):
1080 line = line.strip()
1081 if not line or line.startswith("#"):
1082 continue
1083
1084 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1085 pieces = line.split()
1086 if len(pieces) != 5:
1087 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1088
1089 # Ignore entries that are managed by vold.
1090 options = pieces[4]
1091 if "voldmanaged=" in options:
1092 continue
1093
1094 # It's a good line, parse it.
1095 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001096 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001097 options = options.split(",")
1098 for i in options:
1099 if i.startswith("length="):
1100 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001101 elif i == "slotselect":
1102 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001103 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001104 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001105 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001106
Tao Baod1de6f32017-03-01 16:38:48 -08001107 mount_flags = pieces[3]
1108 # Honor the SELinux context if present.
1109 context = None
1110 for i in mount_flags.split(","):
1111 if i.startswith("context="):
1112 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001113
Tao Baod1de6f32017-03-01 16:38:48 -08001114 mount_point = pieces[1]
1115 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001116 device=pieces[0], length=length, context=context,
1117 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001118
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001119 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001120 # system. Other areas assume system is always at "/system" so point /system
1121 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001122 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001123 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001124 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001125 return d
1126
1127
Tao Bao765668f2019-10-04 22:03:00 -07001128def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1129 """Finds the path to recovery fstab and loads its contents."""
1130 # recovery fstab is only meaningful when installing an update via recovery
1131 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001132 if info_dict.get('ab_update') == 'true' and \
1133 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001134 return None
1135
1136 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1137 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1138 # cases, since it may load the info_dict from an old build (e.g. when
1139 # generating incremental OTAs from that build).
1140 system_root_image = info_dict.get('system_root_image') == 'true'
1141 if info_dict.get('no_recovery') != 'true':
1142 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1143 if isinstance(input_file, zipfile.ZipFile):
1144 if recovery_fstab_path not in input_file.namelist():
1145 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1146 else:
1147 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1148 if not os.path.exists(path):
1149 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1150 return LoadRecoveryFSTab(
1151 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1152 system_root_image)
1153
1154 if info_dict.get('recovery_as_boot') == 'true':
1155 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1156 if isinstance(input_file, zipfile.ZipFile):
1157 if recovery_fstab_path not in input_file.namelist():
1158 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1159 else:
1160 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1161 if not os.path.exists(path):
1162 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1163 return LoadRecoveryFSTab(
1164 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1165 system_root_image)
1166
1167 return None
1168
1169
Doug Zongker37974732010-09-16 17:44:38 -07001170def DumpInfoDict(d):
1171 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001172 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001173
Dan Albert8b72aef2015-03-23 19:13:21 -07001174
Daniel Norman55417142019-11-25 16:04:36 -08001175def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001176 """Merges dynamic partition info variables.
1177
1178 Args:
1179 framework_dict: The dictionary of dynamic partition info variables from the
1180 partial framework target files.
1181 vendor_dict: The dictionary of dynamic partition info variables from the
1182 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001183
1184 Returns:
1185 The merged dynamic partition info dictionary.
1186 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001187
1188 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001189 combined = set(a.split())
1190 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001191 combined = [item.strip() for item in combined if item.strip()]
1192 return " ".join(sorted(combined))
1193
1194 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001195 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001196 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1197
1198 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001199 # For keys-value pairs that are the same, copy to merged dict
1200 for key in vendor_dict.keys():
1201 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1202 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001203
1204 merged_dict["dynamic_partition_list"] = uniq_concat(
1205 framework_dict.get("dynamic_partition_list", ""),
1206 vendor_dict.get("dynamic_partition_list", ""))
1207
1208 # Super block devices are defined by the vendor dict.
1209 if "super_block_devices" in vendor_dict:
1210 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001211 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001212 key = "super_%s_device_size" % block_device
1213 if key not in vendor_dict:
1214 raise ValueError("Vendor dict does not contain required key %s." % key)
1215 merged_dict[key] = vendor_dict[key]
1216
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001217 # Partition groups and group sizes are defined by the vendor dict because
1218 # these values may vary for each board that uses a shared system image.
1219 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001220 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001221 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001222 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001223 if key not in vendor_dict:
1224 raise ValueError("Vendor dict does not contain required key %s." % key)
1225 merged_dict[key] = vendor_dict[key]
1226
1227 # Set the partition group's partition list using a concatenation of the
1228 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001229 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001230 merged_dict[key] = uniq_concat(
1231 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301232
Daniel Normanb0c75912020-09-24 14:30:21 -07001233 # Various other flags should be copied from the vendor dict, if defined.
1234 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1235 "super_metadata_device", "super_partition_error_limit",
1236 "super_partition_size"):
1237 if key in vendor_dict.keys():
1238 merged_dict[key] = vendor_dict[key]
1239
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001240 return merged_dict
1241
1242
Daniel Norman21c34f72020-11-11 17:25:50 -08001243def PartitionMapFromTargetFiles(target_files_dir):
1244 """Builds a map from partition -> path within an extracted target files directory."""
1245 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1246 possible_subdirs = {
1247 "system": ["SYSTEM"],
1248 "vendor": ["VENDOR", "SYSTEM/vendor"],
1249 "product": ["PRODUCT", "SYSTEM/product"],
1250 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1251 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1252 "vendor_dlkm": [
1253 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1254 ],
1255 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001256 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001257 }
1258 partition_map = {}
1259 for partition, subdirs in possible_subdirs.items():
1260 for subdir in subdirs:
1261 if os.path.exists(os.path.join(target_files_dir, subdir)):
1262 partition_map[partition] = subdir
1263 break
1264 return partition_map
1265
1266
Daniel Normand3351562020-10-29 12:33:11 -07001267def SharedUidPartitionViolations(uid_dict, partition_groups):
1268 """Checks for APK sharedUserIds that cross partition group boundaries.
1269
1270 This uses a single or merged build's shareduid_violation_modules.json
1271 output file, as generated by find_shareduid_violation.py or
1272 core/tasks/find-shareduid-violation.mk.
1273
1274 An error is defined as a sharedUserId that is found in a set of partitions
1275 that span more than one partition group.
1276
1277 Args:
1278 uid_dict: A dictionary created by using the standard json module to read a
1279 complete shareduid_violation_modules.json file.
1280 partition_groups: A list of groups, where each group is a list of
1281 partitions.
1282
1283 Returns:
1284 A list of error messages.
1285 """
1286 errors = []
1287 for uid, partitions in uid_dict.items():
1288 found_in_groups = [
1289 group for group in partition_groups
1290 if set(partitions.keys()) & set(group)
1291 ]
1292 if len(found_in_groups) > 1:
1293 errors.append(
1294 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1295 % (uid, ",".join(sorted(partitions.keys()))))
1296 return errors
1297
1298
Daniel Norman21c34f72020-11-11 17:25:50 -08001299def RunHostInitVerifier(product_out, partition_map):
1300 """Runs host_init_verifier on the init rc files within partitions.
1301
1302 host_init_verifier searches the etc/init path within each partition.
1303
1304 Args:
1305 product_out: PRODUCT_OUT directory, containing partition directories.
1306 partition_map: A map of partition name -> relative path within product_out.
1307 """
1308 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1309 cmd = ["host_init_verifier"]
1310 for partition, path in partition_map.items():
1311 if partition not in allowed_partitions:
1312 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1313 partition)
1314 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1315 # Add --property-contexts if the file exists on the partition.
1316 property_contexts = "%s_property_contexts" % (
1317 "plat" if partition == "system" else partition)
1318 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1319 property_contexts)
1320 if os.path.exists(property_contexts_path):
1321 cmd.append("--property-contexts=%s" % property_contexts_path)
1322 # Add the passwd file if the file exists on the partition.
1323 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1324 if os.path.exists(passwd_path):
1325 cmd.extend(["-p", passwd_path])
1326 return RunAndCheckOutput(cmd)
1327
1328
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001329def AppendAVBSigningArgs(cmd, partition):
1330 """Append signing arguments for avbtool."""
1331 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1332 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001333 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1334 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1335 if os.path.exists(new_key_path):
1336 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001337 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1338 if key_path and algorithm:
1339 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001340 avb_salt = OPTIONS.info_dict.get("avb_salt")
1341 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001342 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001343 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001344
1345
Tao Bao765668f2019-10-04 22:03:00 -07001346def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001347 """Returns the VBMeta arguments for partition.
1348
1349 It sets up the VBMeta argument by including the partition descriptor from the
1350 given 'image', or by configuring the partition as a chained partition.
1351
1352 Args:
1353 partition: The name of the partition (e.g. "system").
1354 image: The path to the partition image.
1355 info_dict: A dict returned by common.LoadInfoDict(). Will use
1356 OPTIONS.info_dict if None has been given.
1357
1358 Returns:
1359 A list of VBMeta arguments.
1360 """
1361 if info_dict is None:
1362 info_dict = OPTIONS.info_dict
1363
1364 # Check if chain partition is used.
1365 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001366 if not key_path:
1367 return ["--include_descriptors_from_image", image]
1368
1369 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1370 # into vbmeta.img. The recovery image will be configured on an independent
1371 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1372 # See details at
1373 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001374 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001375 return []
1376
1377 # Otherwise chain the partition into vbmeta.
1378 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1379 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001380
1381
Tao Bao02a08592018-07-22 12:40:45 -07001382def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1383 """Constructs and returns the arg to build or verify a chained partition.
1384
1385 Args:
1386 partition: The partition name.
1387 info_dict: The info dict to look up the key info and rollback index
1388 location.
1389 key: The key to be used for building or verifying the partition. Defaults to
1390 the key listed in info_dict.
1391
1392 Returns:
1393 A string of form "partition:rollback_index_location:key" that can be used to
1394 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001395 """
1396 if key is None:
1397 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001398 if key and not os.path.exists(key) and OPTIONS.search_path:
1399 new_key_path = os.path.join(OPTIONS.search_path, key)
1400 if os.path.exists(new_key_path):
1401 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001402 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001403 rollback_index_location = info_dict[
1404 "avb_" + partition + "_rollback_index_location"]
1405 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1406
1407
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001408def _HasGkiCertificationArgs():
1409 return ("gki_signing_key_path" in OPTIONS.info_dict and
1410 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001411
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001412
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001413def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001414 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001415 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001416
1417 if not os.path.exists(key_path) and OPTIONS.search_path:
1418 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1419 if os.path.exists(new_key_path):
1420 key_path = new_key_path
1421
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001422 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001423 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001424 raise ExternalError(
1425 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001426
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001427 output_certificate = tempfile.NamedTemporaryFile()
1428 cmd = [
1429 "generate_gki_certificate",
1430 "--name", image_name,
1431 "--algorithm", algorithm,
1432 "--key", key_path,
1433 "--output", output_certificate.name,
1434 image,
1435 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001436
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001437 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1438 signature_args = signature_args.strip()
1439 if signature_args:
1440 cmd.extend(["--additional_avb_args", signature_args])
1441
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001442 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001443 args = args.strip()
1444 if args:
1445 cmd.extend(["--additional_avb_args", args])
1446
1447 RunAndCheckOutput(cmd)
1448
1449 output_certificate.seek(os.SEEK_SET, 0)
1450 data = output_certificate.read()
1451 output_certificate.close()
1452 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001453
1454
Daniel Norman276f0622019-07-26 14:13:51 -07001455def BuildVBMeta(image_path, partitions, name, needed_partitions):
1456 """Creates a VBMeta image.
1457
1458 It generates the requested VBMeta image. The requested image could be for
1459 top-level or chained VBMeta image, which is determined based on the name.
1460
1461 Args:
1462 image_path: The output path for the new VBMeta image.
1463 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001464 values. Only valid partition names are accepted, as partitions listed
1465 in common.AVB_PARTITIONS and custom partitions listed in
1466 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001467 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1468 needed_partitions: Partitions whose descriptors should be included into the
1469 generated VBMeta image.
1470
1471 Raises:
1472 AssertionError: On invalid input args.
1473 """
1474 avbtool = OPTIONS.info_dict["avb_avbtool"]
1475 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1476 AppendAVBSigningArgs(cmd, name)
1477
Hongguang Chenf23364d2020-04-27 18:36:36 -07001478 custom_partitions = OPTIONS.info_dict.get(
1479 "avb_custom_images_partition_list", "").strip().split()
1480
Daniel Norman276f0622019-07-26 14:13:51 -07001481 for partition, path in partitions.items():
1482 if partition not in needed_partitions:
1483 continue
1484 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001485 partition in AVB_VBMETA_PARTITIONS or
1486 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001487 'Unknown partition: {}'.format(partition)
1488 assert os.path.exists(path), \
1489 'Failed to find {} for {}'.format(path, partition)
1490 cmd.extend(GetAvbPartitionArg(partition, path))
1491
1492 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1493 if args and args.strip():
1494 split_args = shlex.split(args)
1495 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001496 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001497 # as a path relative to source tree, which may not be available at the
1498 # same location when running this script (we have the input target_files
1499 # zip only). For such cases, we additionally scan other locations (e.g.
1500 # IMAGES/, RADIO/, etc) before bailing out.
1501 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001502 chained_image = split_args[index + 1]
1503 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001504 continue
1505 found = False
1506 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1507 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001508 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001509 if os.path.exists(alt_path):
1510 split_args[index + 1] = alt_path
1511 found = True
1512 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001513 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001514 cmd.extend(split_args)
1515
1516 RunAndCheckOutput(cmd)
1517
1518
jiajia tang836f76b2021-04-02 14:48:26 +08001519def _MakeRamdisk(sourcedir, fs_config_file=None,
1520 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001521 ramdisk_img = tempfile.NamedTemporaryFile()
1522
1523 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1524 cmd = ["mkbootfs", "-f", fs_config_file,
1525 os.path.join(sourcedir, "RAMDISK")]
1526 else:
1527 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1528 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001529 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001530 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001531 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001532 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001533 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001534 else:
1535 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001536
1537 p2.wait()
1538 p1.wait()
1539 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001540 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001541
1542 return ramdisk_img
1543
1544
Steve Muckle9793cf62020-04-08 18:27:00 -07001545def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001546 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001547 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001548
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001549 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001550 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1551 we are building a two-step special image (i.e. building a recovery image to
1552 be loaded into /boot in two-step OTAs).
1553
1554 Return the image data, or None if sourcedir does not appear to contains files
1555 for building the requested image.
1556 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001557
Yifan Hong63c5ca12020-10-08 11:54:02 -07001558 if info_dict is None:
1559 info_dict = OPTIONS.info_dict
1560
Steve Muckle9793cf62020-04-08 18:27:00 -07001561 # "boot" or "recovery", without extension.
1562 partition_name = os.path.basename(sourcedir).lower()
1563
Yifan Hong63c5ca12020-10-08 11:54:02 -07001564 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001565 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001566 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1567 logger.info("Excluded kernel binary from recovery image.")
1568 else:
1569 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001570 elif partition_name == "init_boot":
1571 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001572 else:
1573 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001574 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001575 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001576 return None
1577
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001578 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1579
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001580 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001581 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001582
Doug Zongkereef39442009-04-02 12:14:19 -07001583 img = tempfile.NamedTemporaryFile()
1584
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001585 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001586 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001587 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1588 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001589
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001590 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1591 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1592
Yifan Hong63c5ca12020-10-08 11:54:02 -07001593 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001594 if kernel_path is not None:
1595 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001596
Benoit Fradina45a8682014-07-14 21:00:43 +02001597 fn = os.path.join(sourcedir, "second")
1598 if os.access(fn, os.F_OK):
1599 cmd.append("--second")
1600 cmd.append(fn)
1601
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001602 fn = os.path.join(sourcedir, "dtb")
1603 if os.access(fn, os.F_OK):
1604 cmd.append("--dtb")
1605 cmd.append(fn)
1606
Doug Zongker171f1cd2009-06-15 22:36:37 -07001607 fn = os.path.join(sourcedir, "cmdline")
1608 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001609 cmd.append("--cmdline")
1610 cmd.append(open(fn).read().rstrip("\n"))
1611
1612 fn = os.path.join(sourcedir, "base")
1613 if os.access(fn, os.F_OK):
1614 cmd.append("--base")
1615 cmd.append(open(fn).read().rstrip("\n"))
1616
Ying Wang4de6b5b2010-08-25 14:29:34 -07001617 fn = os.path.join(sourcedir, "pagesize")
1618 if os.access(fn, os.F_OK):
1619 cmd.append("--pagesize")
1620 cmd.append(open(fn).read().rstrip("\n"))
1621
Steve Mucklef84668e2020-03-16 19:13:46 -07001622 if partition_name == "recovery":
1623 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301624 if not args:
1625 # Fall back to "mkbootimg_args" for recovery image
1626 # in case "recovery_mkbootimg_args" is not set.
1627 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001628 elif partition_name == "init_boot":
1629 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001630 else:
1631 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001632 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001633 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001634
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001635 args = info_dict.get("mkbootimg_version_args")
1636 if args and args.strip():
1637 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001638
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001639 if has_ramdisk:
1640 cmd.extend(["--ramdisk", ramdisk_img.name])
1641
Tao Baod95e9fd2015-03-29 23:07:41 -07001642 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001643 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001644 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001645 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001646 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001647 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001648
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001649 if partition_name == "recovery":
1650 if info_dict.get("include_recovery_dtbo") == "true":
1651 fn = os.path.join(sourcedir, "recovery_dtbo")
1652 cmd.extend(["--recovery_dtbo", fn])
1653 if info_dict.get("include_recovery_acpio") == "true":
1654 fn = os.path.join(sourcedir, "recovery_acpio")
1655 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001656
Tao Bao986ee862018-10-04 15:46:16 -07001657 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001658
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001659 if _HasGkiCertificationArgs():
1660 if not os.path.exists(img.name):
1661 raise ValueError("Cannot find GKI boot.img")
1662 if kernel_path is None or not os.path.exists(kernel_path):
1663 raise ValueError("Cannot find GKI kernel.img")
1664
1665 # Certify GKI images.
1666 boot_signature_bytes = b''
1667 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1668 boot_signature_bytes += _GenerateGkiCertificate(
1669 kernel_path, "generic_kernel")
1670
1671 BOOT_SIGNATURE_SIZE = 16 * 1024
1672 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1673 raise ValueError(
1674 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1675 boot_signature_bytes += (
1676 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1677 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1678
1679 with open(img.name, 'ab') as f:
1680 f.write(boot_signature_bytes)
1681
Tao Bao76def242017-11-21 09:25:31 -08001682 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001683 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001684 # Hard-code the path as "/boot" for two-step special recovery image (which
1685 # will be loaded into /boot during the two-step OTA).
1686 if two_step_image:
1687 path = "/boot"
1688 else:
Tao Baobf70c312017-07-11 17:27:55 -07001689 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001690 cmd = [OPTIONS.boot_signer_path]
1691 cmd.extend(OPTIONS.boot_signer_args)
1692 cmd.extend([path, img.name,
1693 info_dict["verity_key"] + ".pk8",
1694 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001695 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001696
Tao Baod95e9fd2015-03-29 23:07:41 -07001697 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001698 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001699 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001700 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001701 # We have switched from the prebuilt futility binary to using the tool
1702 # (futility-host) built from the source. Override the setting in the old
1703 # TF.zip.
1704 futility = info_dict["futility"]
1705 if futility.startswith("prebuilts/"):
1706 futility = "futility-host"
1707 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001708 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001709 info_dict["vboot_key"] + ".vbprivk",
1710 info_dict["vboot_subkey"] + ".vbprivk",
1711 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001712 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001713 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001714
Tao Baof3282b42015-04-01 11:21:55 -07001715 # Clean up the temp files.
1716 img_unsigned.close()
1717 img_keyblock.close()
1718
David Zeuthen8fecb282017-12-01 16:24:01 -05001719 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001720 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001721 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001722 if partition_name == "recovery":
1723 part_size = info_dict["recovery_size"]
1724 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001725 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001726 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001727 "--partition_size", str(part_size), "--partition_name",
1728 partition_name]
1729 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001730 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001731 if args and args.strip():
1732 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001733 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001734
1735 img.seek(os.SEEK_SET, 0)
1736 data = img.read()
1737
1738 if has_ramdisk:
1739 ramdisk_img.close()
1740 img.close()
1741
1742 return data
1743
1744
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001745def _SignBootableImage(image_path, prebuilt_name, partition_name,
1746 info_dict=None):
1747 """Performs AVB signing for a prebuilt boot.img.
1748
1749 Args:
1750 image_path: The full path of the image, e.g., /path/to/boot.img.
1751 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001752 boot-5.10.img, recovery.img or init_boot.img.
1753 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001754 info_dict: The information dict read from misc_info.txt.
1755 """
1756 if info_dict is None:
1757 info_dict = OPTIONS.info_dict
1758
1759 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1760 if info_dict.get("avb_enable") == "true":
1761 avbtool = info_dict["avb_avbtool"]
1762 if partition_name == "recovery":
1763 part_size = info_dict["recovery_size"]
1764 else:
1765 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1766
1767 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1768 "--partition_size", str(part_size), "--partition_name",
1769 partition_name]
1770 AppendAVBSigningArgs(cmd, partition_name)
1771 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1772 if args and args.strip():
1773 cmd.extend(shlex.split(args))
1774 RunAndCheckOutput(cmd)
1775
1776
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001777def HasRamdisk(partition_name, info_dict=None):
1778 """Returns true/false to see if a bootable image should have a ramdisk.
1779
1780 Args:
1781 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1782 info_dict: The information dict read from misc_info.txt.
1783 """
1784 if info_dict is None:
1785 info_dict = OPTIONS.info_dict
1786
1787 if partition_name != "boot":
1788 return True # init_boot.img or recovery.img has a ramdisk.
1789
1790 if info_dict.get("recovery_as_boot") == "true":
1791 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1792
Bowgo Tsai85578e02022-04-19 10:50:59 +08001793 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1794 return False # A GKI boot.img has no ramdisk since Android-13.
1795
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001796 if info_dict.get("system_root_image") == "true":
1797 # The ramdisk content is merged into the system.img, so there is NO
1798 # ramdisk in the boot.img or boot-<kernel version>.img.
1799 return False
1800
1801 if info_dict.get("init_boot") == "true":
1802 # The ramdisk is moved to the init_boot.img, so there is NO
1803 # ramdisk in the boot.img or boot-<kernel version>.img.
1804 return False
1805
1806 return True
1807
1808
Doug Zongkerd5131602012-08-02 14:46:42 -07001809def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001810 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001811 """Return a File object with the desired bootable image.
1812
1813 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1814 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1815 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001816
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001817 if info_dict is None:
1818 info_dict = OPTIONS.info_dict
1819
Doug Zongker55d93282011-01-25 17:03:34 -08001820 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1821 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001822 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001823 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001824
1825 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1826 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001827 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001828 return File.FromLocalFile(name, prebuilt_path)
1829
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001830 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001831 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1832 if os.path.exists(prebuilt_path):
1833 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1834 signed_img = MakeTempFile()
1835 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001836 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1837 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001838
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001839 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001840
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001841 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001842
Doug Zongker6f1d0312014-08-22 08:07:12 -07001843 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001844 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001845 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001846 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001847 if data:
1848 return File(name, data)
1849 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001850
Doug Zongkereef39442009-04-02 12:14:19 -07001851
Lucas Wei03230252022-04-18 16:00:40 +08001852def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001853 """Build a vendor boot image from the specified sourcedir.
1854
1855 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1856 turn them into a vendor boot image.
1857
1858 Return the image data, or None if sourcedir does not appear to contains files
1859 for building the requested image.
1860 """
1861
1862 if info_dict is None:
1863 info_dict = OPTIONS.info_dict
1864
1865 img = tempfile.NamedTemporaryFile()
1866
TJ Rhoades6f488e92022-05-01 22:16:22 -07001867 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001868 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001869
1870 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1871 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1872
1873 cmd = [mkbootimg]
1874
1875 fn = os.path.join(sourcedir, "dtb")
1876 if os.access(fn, os.F_OK):
Lucas Wei03230252022-04-18 16:00:40 +08001877 has_vendor_kernel_boot = (info_dict.get("vendor_kernel_boot", "").lower() == "true")
1878
1879 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1880 # Otherwise pack dtb into vendor_boot.
1881 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1882 cmd.append("--dtb")
1883 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001884
1885 fn = os.path.join(sourcedir, "vendor_cmdline")
1886 if os.access(fn, os.F_OK):
1887 cmd.append("--vendor_cmdline")
1888 cmd.append(open(fn).read().rstrip("\n"))
1889
1890 fn = os.path.join(sourcedir, "base")
1891 if os.access(fn, os.F_OK):
1892 cmd.append("--base")
1893 cmd.append(open(fn).read().rstrip("\n"))
1894
1895 fn = os.path.join(sourcedir, "pagesize")
1896 if os.access(fn, os.F_OK):
1897 cmd.append("--pagesize")
1898 cmd.append(open(fn).read().rstrip("\n"))
1899
1900 args = info_dict.get("mkbootimg_args")
1901 if args and args.strip():
1902 cmd.extend(shlex.split(args))
1903
1904 args = info_dict.get("mkbootimg_version_args")
1905 if args and args.strip():
1906 cmd.extend(shlex.split(args))
1907
1908 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1909 cmd.extend(["--vendor_boot", img.name])
1910
Devin Moore50509012021-01-13 10:45:04 -08001911 fn = os.path.join(sourcedir, "vendor_bootconfig")
1912 if os.access(fn, os.F_OK):
1913 cmd.append("--vendor_bootconfig")
1914 cmd.append(fn)
1915
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001916 ramdisk_fragment_imgs = []
1917 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1918 if os.access(fn, os.F_OK):
1919 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1920 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001921 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1922 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001923 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001924 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1925 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001926 # Use prebuilt image if found, else create ramdisk from supplied files.
1927 if os.access(fn, os.F_OK):
1928 ramdisk_fragment_pathname = fn
1929 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001930 ramdisk_fragment_root = os.path.join(
1931 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001932 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1933 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001934 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1935 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1936 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1937
Steve Mucklee1b10862019-07-10 10:49:37 -07001938 RunAndCheckOutput(cmd)
1939
1940 # AVB: if enabled, calculate and add hash.
1941 if info_dict.get("avb_enable") == "true":
1942 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001943 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001944 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001945 "--partition_size", str(part_size), "--partition_name", partition_name]
1946 AppendAVBSigningArgs(cmd, partition_name)
1947 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001948 if args and args.strip():
1949 cmd.extend(shlex.split(args))
1950 RunAndCheckOutput(cmd)
1951
1952 img.seek(os.SEEK_SET, 0)
1953 data = img.read()
1954
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001955 for f in ramdisk_fragment_imgs:
1956 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001957 ramdisk_img.close()
1958 img.close()
1959
1960 return data
1961
1962
1963def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1964 info_dict=None):
1965 """Return a File object with the desired vendor boot image.
1966
1967 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1968 the source files in 'unpack_dir'/'tree_subdir'."""
1969
1970 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1971 if os.path.exists(prebuilt_path):
1972 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1973 return File.FromLocalFile(name, prebuilt_path)
1974
1975 logger.info("building image from target_files %s...", tree_subdir)
1976
1977 if info_dict is None:
1978 info_dict = OPTIONS.info_dict
1979
Kelvin Zhang0876c412020-06-23 15:06:58 -04001980 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001981 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1982 if data:
1983 return File(name, data)
1984 return None
1985
1986
1987def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1988 info_dict=None):
1989 """Return a File object with the desired vendor kernel boot image.
1990
1991 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1992 the source files in 'unpack_dir'/'tree_subdir'."""
1993
1994 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1995 if os.path.exists(prebuilt_path):
1996 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1997 return File.FromLocalFile(name, prebuilt_path)
1998
1999 logger.info("building image from target_files %s...", tree_subdir)
2000
2001 if info_dict is None:
2002 info_dict = OPTIONS.info_dict
2003
2004 data = _BuildVendorBootImage(
2005 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002006 if data:
2007 return File(name, data)
2008 return None
2009
2010
Narayan Kamatha07bf042017-08-14 14:49:21 +01002011def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002012 """Gunzips the given gzip compressed file to a given output file."""
2013 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002014 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002015 shutil.copyfileobj(in_file, out_file)
2016
2017
Tao Bao0ff15de2019-03-20 11:26:06 -07002018def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002019 """Unzips the archive to the given directory.
2020
2021 Args:
2022 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002023 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002024 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2025 archvie. Non-matching patterns will be filtered out. If there's no match
2026 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002027 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002028 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002029 if patterns is not None:
2030 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002031 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002032 names = input_zip.namelist()
2033 filtered = [
2034 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2035
2036 # There isn't any matching files. Don't unzip anything.
2037 if not filtered:
2038 return
2039 cmd.extend(filtered)
2040
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002041 RunAndCheckOutput(cmd)
2042
2043
Daniel Norman78554ea2021-09-14 10:29:38 -07002044def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002045 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002046
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002047 Args:
2048 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2049 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2050
Daniel Norman78554ea2021-09-14 10:29:38 -07002051 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002052 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002053
Tao Bao1c830bf2017-12-25 10:43:47 -08002054 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002055 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002056 """
Doug Zongkereef39442009-04-02 12:14:19 -07002057
Tao Bao1c830bf2017-12-25 10:43:47 -08002058 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002059 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2060 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002061 UnzipToDir(m.group(1), tmp, patterns)
2062 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002063 filename = m.group(1)
2064 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002065 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002066
Tao Baodba59ee2018-01-09 13:21:02 -08002067 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002068
2069
Yifan Hong8a66a712019-04-04 15:37:57 -07002070def GetUserImage(which, tmpdir, input_zip,
2071 info_dict=None,
2072 allow_shared_blocks=None,
2073 hashtree_info_generator=None,
2074 reset_file_map=False):
2075 """Returns an Image object suitable for passing to BlockImageDiff.
2076
2077 This function loads the specified image from the given path. If the specified
2078 image is sparse, it also performs additional processing for OTA purpose. For
2079 example, it always adds block 0 to clobbered blocks list. It also detects
2080 files that cannot be reconstructed from the block list, for whom we should
2081 avoid applying imgdiff.
2082
2083 Args:
2084 which: The partition name.
2085 tmpdir: The directory that contains the prebuilt image and block map file.
2086 input_zip: The target-files ZIP archive.
2087 info_dict: The dict to be looked up for relevant info.
2088 allow_shared_blocks: If image is sparse, whether having shared blocks is
2089 allowed. If none, it is looked up from info_dict.
2090 hashtree_info_generator: If present and image is sparse, generates the
2091 hashtree_info for this sparse image.
2092 reset_file_map: If true and image is sparse, reset file map before returning
2093 the image.
2094 Returns:
2095 A Image object. If it is a sparse image and reset_file_map is False, the
2096 image will have file_map info loaded.
2097 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002098 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002099 info_dict = LoadInfoDict(input_zip)
2100
2101 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002102 if info_dict.get(which + "_disable_sparse"):
2103 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002104
2105 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2106 # shared blocks (i.e. some blocks will show up in multiple files' block
2107 # list). We can only allocate such shared blocks to the first "owner", and
2108 # disable imgdiff for all later occurrences.
2109 if allow_shared_blocks is None:
2110 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2111
2112 if is_sparse:
2113 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2114 hashtree_info_generator)
2115 if reset_file_map:
2116 img.ResetFileMap()
2117 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002118 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002119
2120
2121def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2122 """Returns a Image object suitable for passing to BlockImageDiff.
2123
2124 This function loads the specified non-sparse image from the given path.
2125
2126 Args:
2127 which: The partition name.
2128 tmpdir: The directory that contains the prebuilt image and block map file.
2129 Returns:
2130 A Image object.
2131 """
2132 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2133 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2134
2135 # The image and map files must have been created prior to calling
2136 # ota_from_target_files.py (since LMP).
2137 assert os.path.exists(path) and os.path.exists(mappath)
2138
Tianjie Xu41976c72019-07-03 13:57:01 -07002139 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2140
Yifan Hong8a66a712019-04-04 15:37:57 -07002141
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002142def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2143 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002144 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2145
2146 This function loads the specified sparse image from the given path, and
2147 performs additional processing for OTA purpose. For example, it always adds
2148 block 0 to clobbered blocks list. It also detects files that cannot be
2149 reconstructed from the block list, for whom we should avoid applying imgdiff.
2150
2151 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002152 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002153 tmpdir: The directory that contains the prebuilt image and block map file.
2154 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002155 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002156 hashtree_info_generator: If present, generates the hashtree_info for this
2157 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002158 Returns:
2159 A SparseImage object, with file_map info loaded.
2160 """
Tao Baoc765cca2018-01-31 17:32:40 -08002161 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2162 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2163
2164 # The image and map files must have been created prior to calling
2165 # ota_from_target_files.py (since LMP).
2166 assert os.path.exists(path) and os.path.exists(mappath)
2167
2168 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2169 # it to clobbered_blocks so that it will be written to the target
2170 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2171 clobbered_blocks = "0"
2172
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002173 image = sparse_img.SparseImage(
2174 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2175 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002176
2177 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2178 # if they contain all zeros. We can't reconstruct such a file from its block
2179 # list. Tag such entries accordingly. (Bug: 65213616)
2180 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002181 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002182 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002183 continue
2184
Tom Cherryd14b8952018-08-09 14:26:00 -07002185 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2186 # filename listed in system.map may contain an additional leading slash
2187 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2188 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002189 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002190 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002191 arcname = entry.lstrip('/')
2192 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002193 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002194 else:
2195 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002196
2197 assert arcname in input_zip.namelist(), \
2198 "Failed to find the ZIP entry for {}".format(entry)
2199
Tao Baoc765cca2018-01-31 17:32:40 -08002200 info = input_zip.getinfo(arcname)
2201 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002202
2203 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002204 # image, check the original block list to determine its completeness. Note
2205 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002206 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002207 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002208
Tao Baoc765cca2018-01-31 17:32:40 -08002209 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2210 ranges.extra['incomplete'] = True
2211
2212 return image
2213
2214
Doug Zongkereef39442009-04-02 12:14:19 -07002215def GetKeyPasswords(keylist):
2216 """Given a list of keys, prompt the user to enter passwords for
2217 those which require them. Return a {key: password} dict. password
2218 will be None if the key has no password."""
2219
Doug Zongker8ce7c252009-05-22 13:34:54 -07002220 no_passwords = []
2221 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002222 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002223 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002224
2225 # sorted() can't compare strings to None, so convert Nones to strings
2226 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002227 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002228 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002229 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002230 continue
2231
T.R. Fullhart37e10522013-03-18 10:31:26 -07002232 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002233 "-inform", "DER", "-nocrypt"],
2234 stdin=devnull.fileno(),
2235 stdout=devnull.fileno(),
2236 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002237 p.communicate()
2238 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002239 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002240 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002241 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002242 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2243 "-inform", "DER", "-passin", "pass:"],
2244 stdin=devnull.fileno(),
2245 stdout=devnull.fileno(),
2246 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002247 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002248 if p.returncode == 0:
2249 # Encrypted key with empty string as password.
2250 key_passwords[k] = ''
2251 elif stderr.startswith('Error decrypting key'):
2252 # Definitely encrypted key.
2253 # It would have said "Error reading key" if it didn't parse correctly.
2254 need_passwords.append(k)
2255 else:
2256 # Potentially, a type of key that openssl doesn't understand.
2257 # We'll let the routines in signapk.jar handle it.
2258 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002259 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002260
T.R. Fullhart37e10522013-03-18 10:31:26 -07002261 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002262 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002263 return key_passwords
2264
2265
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002266def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002267 """Gets the minSdkVersion declared in the APK.
2268
Martin Stjernholm58472e82022-01-07 22:08:47 +00002269 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2270 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002271
2272 Args:
2273 apk_name: The APK filename.
2274
2275 Returns:
2276 The parsed SDK version string.
2277
2278 Raises:
2279 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002280 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002281 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002282 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002283 stderr=subprocess.PIPE)
2284 stdoutdata, stderrdata = proc.communicate()
2285 if proc.returncode != 0:
2286 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002287 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2288 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002289
Tao Baof47bf0f2018-03-21 23:28:51 -07002290 for line in stdoutdata.split("\n"):
2291 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002292 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2293 if m:
2294 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002295 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002296
2297
2298def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002299 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002300
Tao Baof47bf0f2018-03-21 23:28:51 -07002301 If minSdkVersion is set to a codename, it is translated to a number using the
2302 provided map.
2303
2304 Args:
2305 apk_name: The APK filename.
2306
2307 Returns:
2308 The parsed SDK version number.
2309
2310 Raises:
2311 ExternalError: On failing to get the min SDK version number.
2312 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002313 version = GetMinSdkVersion(apk_name)
2314 try:
2315 return int(version)
2316 except ValueError:
2317 # Not a decimal number. Codename?
2318 if version in codename_to_api_level_map:
2319 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002320 raise ExternalError(
2321 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2322 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002323
2324
2325def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002326 codename_to_api_level_map=None, whole_file=False,
2327 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002328 """Sign the input_name zip/jar/apk, producing output_name. Use the
2329 given key and password (the latter may be None if the key does not
2330 have a password.
2331
Doug Zongker951495f2009-08-14 12:44:19 -07002332 If whole_file is true, use the "-w" option to SignApk to embed a
2333 signature that covers the whole file in the archive comment of the
2334 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002335
2336 min_api_level is the API Level (int) of the oldest platform this file may end
2337 up on. If not specified for an APK, the API Level is obtained by interpreting
2338 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2339
2340 codename_to_api_level_map is needed to translate the codename which may be
2341 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002342
2343 Caller may optionally specify extra args to be passed to SignApk, which
2344 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002345 """
Tao Bao76def242017-11-21 09:25:31 -08002346 if codename_to_api_level_map is None:
2347 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002348 if extra_signapk_args is None:
2349 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002350
Alex Klyubin9667b182015-12-10 13:38:50 -08002351 java_library_path = os.path.join(
2352 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2353
Tao Baoe95540e2016-11-08 12:08:53 -08002354 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2355 ["-Djava.library.path=" + java_library_path,
2356 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002357 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002358 if whole_file:
2359 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002360
2361 min_sdk_version = min_api_level
2362 if min_sdk_version is None:
2363 if not whole_file:
2364 min_sdk_version = GetMinSdkVersionInt(
2365 input_name, codename_to_api_level_map)
2366 if min_sdk_version is not None:
2367 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2368
T.R. Fullhart37e10522013-03-18 10:31:26 -07002369 cmd.extend([key + OPTIONS.public_key_suffix,
2370 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002371 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002372
Tao Bao73dd4f42018-10-04 16:25:33 -07002373 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002374 if password is not None:
2375 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002376 stdoutdata, _ = proc.communicate(password)
2377 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002378 raise ExternalError(
2379 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002380 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002381
Doug Zongkereef39442009-04-02 12:14:19 -07002382
Doug Zongker37974732010-09-16 17:44:38 -07002383def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002384 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002385
Tao Bao9dd909e2017-11-14 11:27:32 -08002386 For non-AVB images, raise exception if the data is too big. Print a warning
2387 if the data is nearing the maximum size.
2388
2389 For AVB images, the actual image size should be identical to the limit.
2390
2391 Args:
2392 data: A string that contains all the data for the partition.
2393 target: The partition name. The ".img" suffix is optional.
2394 info_dict: The dict to be looked up for relevant info.
2395 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002396 if target.endswith(".img"):
2397 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002398 mount_point = "/" + target
2399
Ying Wangf8824af2014-06-03 14:07:27 -07002400 fs_type = None
2401 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002402 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002403 if mount_point == "/userdata":
2404 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002405 p = info_dict["fstab"][mount_point]
2406 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002407 device = p.device
2408 if "/" in device:
2409 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002410 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002411 if not fs_type or not limit:
2412 return
Doug Zongkereef39442009-04-02 12:14:19 -07002413
Andrew Boie0f9aec82012-02-14 09:32:52 -08002414 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002415 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2416 # path.
2417 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2418 if size != limit:
2419 raise ExternalError(
2420 "Mismatching image size for %s: expected %d actual %d" % (
2421 target, limit, size))
2422 else:
2423 pct = float(size) * 100.0 / limit
2424 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2425 if pct >= 99.0:
2426 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002427
2428 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002429 logger.warning("\n WARNING: %s\n", msg)
2430 else:
2431 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002432
2433
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002434def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002435 """Parses the APK certs info from a given target-files zip.
2436
2437 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2438 tuple with the following elements: (1) a dictionary that maps packages to
2439 certs (based on the "certificate" and "private_key" attributes in the file;
2440 (2) a string representing the extension of compressed APKs in the target files
2441 (e.g ".gz", ".bro").
2442
2443 Args:
2444 tf_zip: The input target_files ZipFile (already open).
2445
2446 Returns:
2447 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2448 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2449 no compressed APKs.
2450 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002451 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002452 compressed_extension = None
2453
Tao Bao0f990332017-09-08 19:02:54 -07002454 # META/apkcerts.txt contains the info for _all_ the packages known at build
2455 # time. Filter out the ones that are not installed.
2456 installed_files = set()
2457 for name in tf_zip.namelist():
2458 basename = os.path.basename(name)
2459 if basename:
2460 installed_files.add(basename)
2461
Tao Baoda30cfa2017-12-01 16:19:46 -08002462 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002463 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002464 if not line:
2465 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002466 m = re.match(
2467 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002468 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2469 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002470 line)
2471 if not m:
2472 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002473
Tao Bao818ddf52018-01-05 11:17:34 -08002474 matches = m.groupdict()
2475 cert = matches["CERT"]
2476 privkey = matches["PRIVKEY"]
2477 name = matches["NAME"]
2478 this_compressed_extension = matches["COMPRESSED"]
2479
2480 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2481 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2482 if cert in SPECIAL_CERT_STRINGS and not privkey:
2483 certmap[name] = cert
2484 elif (cert.endswith(OPTIONS.public_key_suffix) and
2485 privkey.endswith(OPTIONS.private_key_suffix) and
2486 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2487 certmap[name] = cert[:-public_key_suffix_len]
2488 else:
2489 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2490
2491 if not this_compressed_extension:
2492 continue
2493
2494 # Only count the installed files.
2495 filename = name + '.' + this_compressed_extension
2496 if filename not in installed_files:
2497 continue
2498
2499 # Make sure that all the values in the compression map have the same
2500 # extension. We don't support multiple compression methods in the same
2501 # system image.
2502 if compressed_extension:
2503 if this_compressed_extension != compressed_extension:
2504 raise ValueError(
2505 "Multiple compressed extensions: {} vs {}".format(
2506 compressed_extension, this_compressed_extension))
2507 else:
2508 compressed_extension = this_compressed_extension
2509
2510 return (certmap,
2511 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002512
2513
Doug Zongkereef39442009-04-02 12:14:19 -07002514COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002515Global options
2516
2517 -p (--path) <dir>
2518 Prepend <dir>/bin to the list of places to search for binaries run by this
2519 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002520
Doug Zongker05d3dea2009-06-22 11:32:31 -07002521 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002522 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002523
Tao Bao30df8b42018-04-23 15:32:53 -07002524 -x (--extra) <key=value>
2525 Add a key/value pair to the 'extras' dict, which device-specific extension
2526 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002527
Doug Zongkereef39442009-04-02 12:14:19 -07002528 -v (--verbose)
2529 Show command lines being executed.
2530
2531 -h (--help)
2532 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002533
2534 --logfile <file>
2535 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002536"""
2537
Kelvin Zhang0876c412020-06-23 15:06:58 -04002538
Doug Zongkereef39442009-04-02 12:14:19 -07002539def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002540 print(docstring.rstrip("\n"))
2541 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002542
2543
2544def ParseOptions(argv,
2545 docstring,
2546 extra_opts="", extra_long_opts=(),
2547 extra_option_handler=None):
2548 """Parse the options in argv and return any arguments that aren't
2549 flags. docstring is the calling module's docstring, to be displayed
2550 for errors and -h. extra_opts and extra_long_opts are for flags
2551 defined by the caller, which are processed by passing them to
2552 extra_option_handler."""
2553
2554 try:
2555 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002556 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002557 ["help", "verbose", "path=", "signapk_path=",
Martin Stjernholm58472e82022-01-07 22:08:47 +00002558 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002559 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002560 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2561 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002562 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002563 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002564 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002565 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002566 sys.exit(2)
2567
Doug Zongkereef39442009-04-02 12:14:19 -07002568 for o, a in opts:
2569 if o in ("-h", "--help"):
2570 Usage(docstring)
2571 sys.exit()
2572 elif o in ("-v", "--verbose"):
2573 OPTIONS.verbose = True
2574 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002575 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002576 elif o in ("--signapk_path",):
2577 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002578 elif o in ("--signapk_shared_library_path",):
2579 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002580 elif o in ("--extra_signapk_args",):
2581 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002582 elif o in ("--aapt2_path",):
2583 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002584 elif o in ("--java_path",):
2585 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002586 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002587 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002588 elif o in ("--android_jar_path",):
2589 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002590 elif o in ("--public_key_suffix",):
2591 OPTIONS.public_key_suffix = a
2592 elif o in ("--private_key_suffix",):
2593 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002594 elif o in ("--boot_signer_path",):
2595 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002596 elif o in ("--boot_signer_args",):
2597 OPTIONS.boot_signer_args = shlex.split(a)
2598 elif o in ("--verity_signer_path",):
2599 OPTIONS.verity_signer_path = a
2600 elif o in ("--verity_signer_args",):
2601 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002602 elif o in ("-s", "--device_specific"):
2603 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002604 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002605 key, value = a.split("=", 1)
2606 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002607 elif o in ("--logfile",):
2608 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002609 else:
2610 if extra_option_handler is None or not extra_option_handler(o, a):
2611 assert False, "unknown option \"%s\"" % (o,)
2612
Doug Zongker85448772014-09-09 14:59:20 -07002613 if OPTIONS.search_path:
2614 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2615 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002616
2617 return args
2618
2619
Tao Bao4c851b12016-09-19 13:54:38 -07002620def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002621 """Make a temp file and add it to the list of things to be deleted
2622 when Cleanup() is called. Return the filename."""
2623 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2624 os.close(fd)
2625 OPTIONS.tempfiles.append(fn)
2626 return fn
2627
2628
Tao Bao1c830bf2017-12-25 10:43:47 -08002629def MakeTempDir(prefix='tmp', suffix=''):
2630 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2631
2632 Returns:
2633 The absolute pathname of the new directory.
2634 """
2635 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2636 OPTIONS.tempfiles.append(dir_name)
2637 return dir_name
2638
2639
Doug Zongkereef39442009-04-02 12:14:19 -07002640def Cleanup():
2641 for i in OPTIONS.tempfiles:
2642 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002643 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002644 else:
2645 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002646 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002647
2648
2649class PasswordManager(object):
2650 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002651 self.editor = os.getenv("EDITOR")
2652 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002653
2654 def GetPasswords(self, items):
2655 """Get passwords corresponding to each string in 'items',
2656 returning a dict. (The dict may have keys in addition to the
2657 values in 'items'.)
2658
2659 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2660 user edit that file to add more needed passwords. If no editor is
2661 available, or $ANDROID_PW_FILE isn't define, prompts the user
2662 interactively in the ordinary way.
2663 """
2664
2665 current = self.ReadFile()
2666
2667 first = True
2668 while True:
2669 missing = []
2670 for i in items:
2671 if i not in current or not current[i]:
2672 missing.append(i)
2673 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002674 if not missing:
2675 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002676
2677 for i in missing:
2678 current[i] = ""
2679
2680 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002681 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002682 if sys.version_info[0] >= 3:
2683 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002684 answer = raw_input("try to edit again? [y]> ").strip()
2685 if answer and answer[0] not in 'yY':
2686 raise RuntimeError("key passwords unavailable")
2687 first = False
2688
2689 current = self.UpdateAndReadFile(current)
2690
Kelvin Zhang0876c412020-06-23 15:06:58 -04002691 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002692 """Prompt the user to enter a value (password) for each key in
2693 'current' whose value is fales. Returns a new dict with all the
2694 values.
2695 """
2696 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002697 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002698 if v:
2699 result[k] = v
2700 else:
2701 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002702 result[k] = getpass.getpass(
2703 "Enter password for %s key> " % k).strip()
2704 if result[k]:
2705 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002706 return result
2707
2708 def UpdateAndReadFile(self, current):
2709 if not self.editor or not self.pwfile:
2710 return self.PromptResult(current)
2711
2712 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002713 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002714 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2715 f.write("# (Additional spaces are harmless.)\n\n")
2716
2717 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002718 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002719 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002720 f.write("[[[ %s ]]] %s\n" % (v, k))
2721 if not v and first_line is None:
2722 # position cursor on first line with no password.
2723 first_line = i + 4
2724 f.close()
2725
Tao Bao986ee862018-10-04 15:46:16 -07002726 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002727
2728 return self.ReadFile()
2729
2730 def ReadFile(self):
2731 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002732 if self.pwfile is None:
2733 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002734 try:
2735 f = open(self.pwfile, "r")
2736 for line in f:
2737 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002738 if not line or line[0] == '#':
2739 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002740 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2741 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002742 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002743 else:
2744 result[m.group(2)] = m.group(1)
2745 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002746 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002747 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002748 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002749 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002750
2751
Dan Albert8e0178d2015-01-27 15:53:15 -08002752def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2753 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002754
2755 # http://b/18015246
2756 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2757 # for files larger than 2GiB. We can work around this by adjusting their
2758 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2759 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2760 # it isn't clear to me exactly what circumstances cause this).
2761 # `zipfile.write()` must be used directly to work around this.
2762 #
2763 # This mess can be avoided if we port to python3.
2764 saved_zip64_limit = zipfile.ZIP64_LIMIT
2765 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2766
2767 if compress_type is None:
2768 compress_type = zip_file.compression
2769 if arcname is None:
2770 arcname = filename
2771
2772 saved_stat = os.stat(filename)
2773
2774 try:
2775 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2776 # file to be zipped and reset it when we're done.
2777 os.chmod(filename, perms)
2778
2779 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002780 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2781 # intentional. zip stores datetimes in local time without a time zone
2782 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2783 # in the zip archive.
2784 local_epoch = datetime.datetime.fromtimestamp(0)
2785 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002786 os.utime(filename, (timestamp, timestamp))
2787
2788 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2789 finally:
2790 os.chmod(filename, saved_stat.st_mode)
2791 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2792 zipfile.ZIP64_LIMIT = saved_zip64_limit
2793
2794
Tao Bao58c1b962015-05-20 09:32:18 -07002795def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002796 compress_type=None):
2797 """Wrap zipfile.writestr() function to work around the zip64 limit.
2798
2799 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2800 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2801 when calling crc32(bytes).
2802
2803 But it still works fine to write a shorter string into a large zip file.
2804 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2805 when we know the string won't be too long.
2806 """
2807
2808 saved_zip64_limit = zipfile.ZIP64_LIMIT
2809 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2810
2811 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2812 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002813 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002814 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002815 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002816 else:
Tao Baof3282b42015-04-01 11:21:55 -07002817 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002818 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2819 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2820 # such a case (since
2821 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2822 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2823 # permission bits. We follow the logic in Python 3 to get consistent
2824 # behavior between using the two versions.
2825 if not zinfo.external_attr:
2826 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002827
2828 # If compress_type is given, it overrides the value in zinfo.
2829 if compress_type is not None:
2830 zinfo.compress_type = compress_type
2831
Tao Bao58c1b962015-05-20 09:32:18 -07002832 # If perms is given, it has a priority.
2833 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002834 # If perms doesn't set the file type, mark it as a regular file.
2835 if perms & 0o770000 == 0:
2836 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002837 zinfo.external_attr = perms << 16
2838
Tao Baof3282b42015-04-01 11:21:55 -07002839 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002840 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2841
Dan Albert8b72aef2015-03-23 19:13:21 -07002842 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002843 zipfile.ZIP64_LIMIT = saved_zip64_limit
2844
2845
Tao Bao89d7ab22017-12-14 17:05:33 -08002846def ZipDelete(zip_filename, entries):
2847 """Deletes entries from a ZIP file.
2848
2849 Since deleting entries from a ZIP file is not supported, it shells out to
2850 'zip -d'.
2851
2852 Args:
2853 zip_filename: The name of the ZIP file.
2854 entries: The name of the entry, or the list of names to be deleted.
2855
2856 Raises:
2857 AssertionError: In case of non-zero return from 'zip'.
2858 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002859 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002860 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002861 # If list is empty, nothing to do
2862 if not entries:
2863 return
Tao Bao89d7ab22017-12-14 17:05:33 -08002864 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002865 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002866
2867
Tao Baof3282b42015-04-01 11:21:55 -07002868def ZipClose(zip_file):
2869 # http://b/18015246
2870 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2871 # central directory.
2872 saved_zip64_limit = zipfile.ZIP64_LIMIT
2873 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2874
2875 zip_file.close()
2876
2877 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002878
2879
2880class DeviceSpecificParams(object):
2881 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002882
Doug Zongker05d3dea2009-06-22 11:32:31 -07002883 def __init__(self, **kwargs):
2884 """Keyword arguments to the constructor become attributes of this
2885 object, which is passed to all functions in the device-specific
2886 module."""
Tao Bao38884282019-07-10 22:20:56 -07002887 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002888 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002889 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002890
2891 if self.module is None:
2892 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002893 if not path:
2894 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002895 try:
2896 if os.path.isdir(path):
2897 info = imp.find_module("releasetools", [path])
2898 else:
2899 d, f = os.path.split(path)
2900 b, x = os.path.splitext(f)
2901 if x == ".py":
2902 f = b
2903 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002904 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002905 self.module = imp.load_module("device_specific", *info)
2906 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002907 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002908
2909 def _DoCall(self, function_name, *args, **kwargs):
2910 """Call the named function in the device-specific module, passing
2911 the given args and kwargs. The first argument to the call will be
2912 the DeviceSpecific object itself. If there is no module, or the
2913 module does not define the function, return the value of the
2914 'default' kwarg (which itself defaults to None)."""
2915 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002916 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002917 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2918
2919 def FullOTA_Assertions(self):
2920 """Called after emitting the block of assertions at the top of a
2921 full OTA package. Implementations can add whatever additional
2922 assertions they like."""
2923 return self._DoCall("FullOTA_Assertions")
2924
Doug Zongkere5ff5902012-01-17 10:55:37 -08002925 def FullOTA_InstallBegin(self):
2926 """Called at the start of full OTA installation."""
2927 return self._DoCall("FullOTA_InstallBegin")
2928
Yifan Hong10c530d2018-12-27 17:34:18 -08002929 def FullOTA_GetBlockDifferences(self):
2930 """Called during full OTA installation and verification.
2931 Implementation should return a list of BlockDifference objects describing
2932 the update on each additional partitions.
2933 """
2934 return self._DoCall("FullOTA_GetBlockDifferences")
2935
Doug Zongker05d3dea2009-06-22 11:32:31 -07002936 def FullOTA_InstallEnd(self):
2937 """Called at the end of full OTA installation; typically this is
2938 used to install the image for the device's baseband processor."""
2939 return self._DoCall("FullOTA_InstallEnd")
2940
2941 def IncrementalOTA_Assertions(self):
2942 """Called after emitting the block of assertions at the top of an
2943 incremental OTA package. Implementations can add whatever
2944 additional assertions they like."""
2945 return self._DoCall("IncrementalOTA_Assertions")
2946
Doug Zongkere5ff5902012-01-17 10:55:37 -08002947 def IncrementalOTA_VerifyBegin(self):
2948 """Called at the start of the verification phase of incremental
2949 OTA installation; additional checks can be placed here to abort
2950 the script before any changes are made."""
2951 return self._DoCall("IncrementalOTA_VerifyBegin")
2952
Doug Zongker05d3dea2009-06-22 11:32:31 -07002953 def IncrementalOTA_VerifyEnd(self):
2954 """Called at the end of the verification phase of incremental OTA
2955 installation; additional checks can be placed here to abort the
2956 script before any changes are made."""
2957 return self._DoCall("IncrementalOTA_VerifyEnd")
2958
Doug Zongkere5ff5902012-01-17 10:55:37 -08002959 def IncrementalOTA_InstallBegin(self):
2960 """Called at the start of incremental OTA installation (after
2961 verification is complete)."""
2962 return self._DoCall("IncrementalOTA_InstallBegin")
2963
Yifan Hong10c530d2018-12-27 17:34:18 -08002964 def IncrementalOTA_GetBlockDifferences(self):
2965 """Called during incremental OTA installation and verification.
2966 Implementation should return a list of BlockDifference objects describing
2967 the update on each additional partitions.
2968 """
2969 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2970
Doug Zongker05d3dea2009-06-22 11:32:31 -07002971 def IncrementalOTA_InstallEnd(self):
2972 """Called at the end of incremental OTA installation; typically
2973 this is used to install the image for the device's baseband
2974 processor."""
2975 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002976
Tao Bao9bc6bb22015-11-09 16:58:28 -08002977 def VerifyOTA_Assertions(self):
2978 return self._DoCall("VerifyOTA_Assertions")
2979
Tao Bao76def242017-11-21 09:25:31 -08002980
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002981class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002982 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002983 self.name = name
2984 self.data = data
2985 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002986 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002987 self.sha1 = sha1(data).hexdigest()
2988
2989 @classmethod
2990 def FromLocalFile(cls, name, diskname):
2991 f = open(diskname, "rb")
2992 data = f.read()
2993 f.close()
2994 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002995
2996 def WriteToTemp(self):
2997 t = tempfile.NamedTemporaryFile()
2998 t.write(self.data)
2999 t.flush()
3000 return t
3001
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003002 def WriteToDir(self, d):
3003 with open(os.path.join(d, self.name), "wb") as fp:
3004 fp.write(self.data)
3005
Geremy Condra36bd3652014-02-06 19:45:10 -08003006 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003007 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003008
Tao Bao76def242017-11-21 09:25:31 -08003009
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003010DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003011 ".gz": "imgdiff",
3012 ".zip": ["imgdiff", "-z"],
3013 ".jar": ["imgdiff", "-z"],
3014 ".apk": ["imgdiff", "-z"],
3015 ".img": "imgdiff",
3016}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003017
Tao Bao76def242017-11-21 09:25:31 -08003018
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003019class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003020 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003021 self.tf = tf
3022 self.sf = sf
3023 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003024 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003025
3026 def ComputePatch(self):
3027 """Compute the patch (as a string of data) needed to turn sf into
3028 tf. Returns the same tuple as GetPatch()."""
3029
3030 tf = self.tf
3031 sf = self.sf
3032
Doug Zongker24cd2802012-08-14 16:36:15 -07003033 if self.diff_program:
3034 diff_program = self.diff_program
3035 else:
3036 ext = os.path.splitext(tf.name)[1]
3037 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003038
3039 ttemp = tf.WriteToTemp()
3040 stemp = sf.WriteToTemp()
3041
3042 ext = os.path.splitext(tf.name)[1]
3043
3044 try:
3045 ptemp = tempfile.NamedTemporaryFile()
3046 if isinstance(diff_program, list):
3047 cmd = copy.copy(diff_program)
3048 else:
3049 cmd = [diff_program]
3050 cmd.append(stemp.name)
3051 cmd.append(ttemp.name)
3052 cmd.append(ptemp.name)
3053 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003054 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003055
Doug Zongkerf8340082014-08-05 10:39:37 -07003056 def run():
3057 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003058 if e:
3059 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003060 th = threading.Thread(target=run)
3061 th.start()
3062 th.join(timeout=300) # 5 mins
3063 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003064 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003065 p.terminate()
3066 th.join(5)
3067 if th.is_alive():
3068 p.kill()
3069 th.join()
3070
Tianjie Xua2a9f992018-01-05 15:15:54 -08003071 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003072 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003073 self.patch = None
3074 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003075 diff = ptemp.read()
3076 finally:
3077 ptemp.close()
3078 stemp.close()
3079 ttemp.close()
3080
3081 self.patch = diff
3082 return self.tf, self.sf, self.patch
3083
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003084 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003085 """Returns a tuple of (target_file, source_file, patch_data).
3086
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003087 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003088 computing the patch failed.
3089 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003090 return self.tf, self.sf, self.patch
3091
3092
3093def ComputeDifferences(diffs):
3094 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003095 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003096
3097 # Do the largest files first, to try and reduce the long-pole effect.
3098 by_size = [(i.tf.size, i) for i in diffs]
3099 by_size.sort(reverse=True)
3100 by_size = [i[1] for i in by_size]
3101
3102 lock = threading.Lock()
3103 diff_iter = iter(by_size) # accessed under lock
3104
3105 def worker():
3106 try:
3107 lock.acquire()
3108 for d in diff_iter:
3109 lock.release()
3110 start = time.time()
3111 d.ComputePatch()
3112 dur = time.time() - start
3113 lock.acquire()
3114
3115 tf, sf, patch = d.GetPatch()
3116 if sf.name == tf.name:
3117 name = tf.name
3118 else:
3119 name = "%s (%s)" % (tf.name, sf.name)
3120 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003121 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003122 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003123 logger.info(
3124 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3125 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003126 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003127 except Exception:
3128 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003129 raise
3130
3131 # start worker threads; wait for them all to finish.
3132 threads = [threading.Thread(target=worker)
3133 for i in range(OPTIONS.worker_threads)]
3134 for th in threads:
3135 th.start()
3136 while threads:
3137 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003138
3139
Dan Albert8b72aef2015-03-23 19:13:21 -07003140class BlockDifference(object):
3141 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003142 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003143 self.tgt = tgt
3144 self.src = src
3145 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003146 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003147 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003148
Tao Baodd2a5892015-03-12 12:32:37 -07003149 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003150 version = max(
3151 int(i) for i in
3152 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003153 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003154 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003155
Tianjie Xu41976c72019-07-03 13:57:01 -07003156 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3157 version=self.version,
3158 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003159 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003160 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003161 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003162 self.touched_src_ranges = b.touched_src_ranges
3163 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003164
Yifan Hong10c530d2018-12-27 17:34:18 -08003165 # On devices with dynamic partitions, for new partitions,
3166 # src is None but OPTIONS.source_info_dict is not.
3167 if OPTIONS.source_info_dict is None:
3168 is_dynamic_build = OPTIONS.info_dict.get(
3169 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003170 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003171 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003172 is_dynamic_build = OPTIONS.source_info_dict.get(
3173 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003174 is_dynamic_source = partition in shlex.split(
3175 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003176
Yifan Hongbb2658d2019-01-25 12:30:58 -08003177 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003178 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3179
Yifan Hongbb2658d2019-01-25 12:30:58 -08003180 # For dynamic partitions builds, check partition list in both source
3181 # and target build because new partitions may be added, and existing
3182 # partitions may be removed.
3183 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3184
Yifan Hong10c530d2018-12-27 17:34:18 -08003185 if is_dynamic:
3186 self.device = 'map_partition("%s")' % partition
3187 else:
3188 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003189 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3190 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003191 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003192 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3193 OPTIONS.source_info_dict)
3194 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003195
Tao Baod8d14be2016-02-04 14:26:02 -08003196 @property
3197 def required_cache(self):
3198 return self._required_cache
3199
Tao Bao76def242017-11-21 09:25:31 -08003200 def WriteScript(self, script, output_zip, progress=None,
3201 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003202 if not self.src:
3203 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003204 script.Print("Patching %s image unconditionally..." % (self.partition,))
3205 else:
3206 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003207
Dan Albert8b72aef2015-03-23 19:13:21 -07003208 if progress:
3209 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003210 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003211
3212 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003213 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003214
Tao Bao9bc6bb22015-11-09 16:58:28 -08003215 def WriteStrictVerifyScript(self, script):
3216 """Verify all the blocks in the care_map, including clobbered blocks.
3217
3218 This differs from the WriteVerifyScript() function: a) it prints different
3219 error messages; b) it doesn't allow half-way updated images to pass the
3220 verification."""
3221
3222 partition = self.partition
3223 script.Print("Verifying %s..." % (partition,))
3224 ranges = self.tgt.care_map
3225 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003226 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003227 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3228 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003229 self.device, ranges_str,
3230 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003231 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003232 script.AppendExtra("")
3233
Tao Baod522bdc2016-04-12 15:53:16 -07003234 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003235 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003236
3237 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003238 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003239 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003240
3241 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003242 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003243 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003244 ranges = self.touched_src_ranges
3245 expected_sha1 = self.touched_src_sha1
3246 else:
3247 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3248 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003249
3250 # No blocks to be checked, skipping.
3251 if not ranges:
3252 return
3253
Tao Bao5ece99d2015-05-12 11:42:31 -07003254 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003255 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003256 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003257 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3258 '"%s.patch.dat")) then' % (
3259 self.device, ranges_str, expected_sha1,
3260 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003261 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003262 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003263
Tianjie Xufc3422a2015-12-15 11:53:59 -08003264 if self.version >= 4:
3265
3266 # Bug: 21124327
3267 # When generating incrementals for the system and vendor partitions in
3268 # version 4 or newer, explicitly check the first block (which contains
3269 # the superblock) of the partition to see if it's what we expect. If
3270 # this check fails, give an explicit log message about the partition
3271 # having been remounted R/W (the most likely explanation).
3272 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003273 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003274
3275 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003276 if partition == "system":
3277 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3278 else:
3279 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003280 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003281 'ifelse (block_image_recover({device}, "{ranges}") && '
3282 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003283 'package_extract_file("{partition}.transfer.list"), '
3284 '"{partition}.new.dat", "{partition}.patch.dat"), '
3285 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003286 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003287 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003288 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003289
Tao Baodd2a5892015-03-12 12:32:37 -07003290 # Abort the OTA update. Note that the incremental OTA cannot be applied
3291 # even if it may match the checksum of the target partition.
3292 # a) If version < 3, operations like move and erase will make changes
3293 # unconditionally and damage the partition.
3294 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003295 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003296 if partition == "system":
3297 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3298 else:
3299 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3300 script.AppendExtra((
3301 'abort("E%d: %s partition has unexpected contents");\n'
3302 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003303
Yifan Hong10c530d2018-12-27 17:34:18 -08003304 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003305 partition = self.partition
3306 script.Print('Verifying the updated %s image...' % (partition,))
3307 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3308 ranges = self.tgt.care_map
3309 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003310 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003311 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003312 self.device, ranges_str,
3313 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003314
3315 # Bug: 20881595
3316 # Verify that extended blocks are really zeroed out.
3317 if self.tgt.extended:
3318 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003319 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003320 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003321 self.device, ranges_str,
3322 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003323 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003324 if partition == "system":
3325 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3326 else:
3327 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003328 script.AppendExtra(
3329 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003330 ' abort("E%d: %s partition has unexpected non-zero contents after '
3331 'OTA update");\n'
3332 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003333 else:
3334 script.Print('Verified the updated %s image.' % (partition,))
3335
Tianjie Xu209db462016-05-24 17:34:52 -07003336 if partition == "system":
3337 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3338 else:
3339 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3340
Tao Bao5fcaaef2015-06-01 13:40:49 -07003341 script.AppendExtra(
3342 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003343 ' abort("E%d: %s partition has unexpected contents after OTA '
3344 'update");\n'
3345 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003346
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003347 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003348 ZipWrite(output_zip,
3349 '{}.transfer.list'.format(self.path),
3350 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003351
Tao Bao76def242017-11-21 09:25:31 -08003352 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3353 # its size. Quailty 9 almost triples the compression time but doesn't
3354 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003355 # zip | brotli(quality 6) | brotli(quality 9)
3356 # compressed_size: 942M | 869M (~8% reduced) | 854M
3357 # compression_time: 75s | 265s | 719s
3358 # decompression_time: 15s | 25s | 25s
3359
3360 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003361 brotli_cmd = ['brotli', '--quality=6',
3362 '--output={}.new.dat.br'.format(self.path),
3363 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003364 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003365 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003366
3367 new_data_name = '{}.new.dat.br'.format(self.partition)
3368 ZipWrite(output_zip,
3369 '{}.new.dat.br'.format(self.path),
3370 new_data_name,
3371 compress_type=zipfile.ZIP_STORED)
3372 else:
3373 new_data_name = '{}.new.dat'.format(self.partition)
3374 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3375
Dan Albert8e0178d2015-01-27 15:53:15 -08003376 ZipWrite(output_zip,
3377 '{}.patch.dat'.format(self.path),
3378 '{}.patch.dat'.format(self.partition),
3379 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003380
Tianjie Xu209db462016-05-24 17:34:52 -07003381 if self.partition == "system":
3382 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3383 else:
3384 code = ErrorCode.VENDOR_UPDATE_FAILURE
3385
Yifan Hong10c530d2018-12-27 17:34:18 -08003386 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003387 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003388 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003389 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003390 device=self.device, partition=self.partition,
3391 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003392 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003393
Kelvin Zhang0876c412020-06-23 15:06:58 -04003394 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003395 data = source.ReadRangeSet(ranges)
3396 ctx = sha1()
3397
3398 for p in data:
3399 ctx.update(p)
3400
3401 return ctx.hexdigest()
3402
Kelvin Zhang0876c412020-06-23 15:06:58 -04003403 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003404 """Return the hash value for all zero blocks."""
3405 zero_block = '\x00' * 4096
3406 ctx = sha1()
3407 for _ in range(num_blocks):
3408 ctx.update(zero_block)
3409
3410 return ctx.hexdigest()
3411
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003412
Tianjie Xu41976c72019-07-03 13:57:01 -07003413# Expose these two classes to support vendor-specific scripts
3414DataImage = images.DataImage
3415EmptyImage = images.EmptyImage
3416
Tao Bao76def242017-11-21 09:25:31 -08003417
Doug Zongker96a57e72010-09-26 14:57:41 -07003418# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003419PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003420 "ext4": "EMMC",
3421 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003422 "f2fs": "EMMC",
3423 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003424}
Doug Zongker96a57e72010-09-26 14:57:41 -07003425
Kelvin Zhang0876c412020-06-23 15:06:58 -04003426
Yifan Hongbdb32012020-05-07 12:38:53 -07003427def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3428 """
3429 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3430 backwards compatibility. It aborts if the fstab entry has slotselect option
3431 (unless check_no_slot is explicitly set to False).
3432 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003433 fstab = info["fstab"]
3434 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003435 if check_no_slot:
3436 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003437 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003438 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3439 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003440 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003441
3442
Yifan Hongbdb32012020-05-07 12:38:53 -07003443def GetTypeAndDeviceExpr(mount_point, info):
3444 """
3445 Return the filesystem of the partition, and an edify expression that evaluates
3446 to the device at runtime.
3447 """
3448 fstab = info["fstab"]
3449 if fstab:
3450 p = fstab[mount_point]
3451 device_expr = '"%s"' % fstab[mount_point].device
3452 if p.slotselect:
3453 device_expr = 'add_slot_suffix(%s)' % device_expr
3454 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003455 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003456
3457
3458def GetEntryForDevice(fstab, device):
3459 """
3460 Returns:
3461 The first entry in fstab whose device is the given value.
3462 """
3463 if not fstab:
3464 return None
3465 for mount_point in fstab:
3466 if fstab[mount_point].device == device:
3467 return fstab[mount_point]
3468 return None
3469
Kelvin Zhang0876c412020-06-23 15:06:58 -04003470
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003471def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003472 """Parses and converts a PEM-encoded certificate into DER-encoded.
3473
3474 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3475
3476 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003477 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003478 """
3479 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003480 save = False
3481 for line in data.split("\n"):
3482 if "--END CERTIFICATE--" in line:
3483 break
3484 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003485 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003486 if "--BEGIN CERTIFICATE--" in line:
3487 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003488 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003489 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003490
Tao Bao04e1f012018-02-04 12:13:35 -08003491
3492def ExtractPublicKey(cert):
3493 """Extracts the public key (PEM-encoded) from the given certificate file.
3494
3495 Args:
3496 cert: The certificate filename.
3497
3498 Returns:
3499 The public key string.
3500
3501 Raises:
3502 AssertionError: On non-zero return from 'openssl'.
3503 """
3504 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3505 # While openssl 1.1 writes the key into the given filename followed by '-out',
3506 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3507 # stdout instead.
3508 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3509 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3510 pubkey, stderrdata = proc.communicate()
3511 assert proc.returncode == 0, \
3512 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3513 return pubkey
3514
3515
Tao Bao1ac886e2019-06-26 11:58:22 -07003516def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003517 """Extracts the AVB public key from the given public or private key.
3518
3519 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003520 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003521 key: The input key file, which should be PEM-encoded public or private key.
3522
3523 Returns:
3524 The path to the extracted AVB public key file.
3525 """
3526 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3527 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003528 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003529 return output
3530
3531
Doug Zongker412c02f2014-02-13 10:58:24 -08003532def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3533 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003534 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003535
Tao Bao6d5d6232018-03-09 17:04:42 -08003536 Most of the space in the boot and recovery images is just the kernel, which is
3537 identical for the two, so the resulting patch should be efficient. Add it to
3538 the output zip, along with a shell script that is run from init.rc on first
3539 boot to actually do the patching and install the new recovery image.
3540
3541 Args:
3542 input_dir: The top-level input directory of the target-files.zip.
3543 output_sink: The callback function that writes the result.
3544 recovery_img: File object for the recovery image.
3545 boot_img: File objects for the boot image.
3546 info_dict: A dict returned by common.LoadInfoDict() on the input
3547 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003548 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003549 if info_dict is None:
3550 info_dict = OPTIONS.info_dict
3551
Tao Bao6d5d6232018-03-09 17:04:42 -08003552 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003553 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3554
3555 if board_uses_vendorimage:
3556 # In this case, the output sink is rooted at VENDOR
3557 recovery_img_path = "etc/recovery.img"
3558 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3559 sh_dir = "bin"
3560 else:
3561 # In this case the output sink is rooted at SYSTEM
3562 recovery_img_path = "vendor/etc/recovery.img"
3563 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3564 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003565
Tao Baof2cffbd2015-07-22 12:33:18 -07003566 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003567 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003568
3569 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003570 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003571 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003572 # With system-root-image, boot and recovery images will have mismatching
3573 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3574 # to handle such a case.
3575 if system_root_image:
3576 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003577 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003578 assert not os.path.exists(path)
3579 else:
3580 diff_program = ["imgdiff"]
3581 if os.path.exists(path):
3582 diff_program.append("-b")
3583 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003584 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003585 else:
3586 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003587
3588 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3589 _, _, patch = d.ComputePatch()
3590 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003591
Dan Albertebb19aa2015-03-27 19:11:53 -07003592 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003593 # The following GetTypeAndDevice()s need to use the path in the target
3594 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003595 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3596 check_no_slot=False)
3597 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3598 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003599 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003600 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003601
Tao Baof2cffbd2015-07-22 12:33:18 -07003602 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003603
3604 # Note that we use /vendor to refer to the recovery resources. This will
3605 # work for a separate vendor partition mounted at /vendor or a
3606 # /system/vendor subdirectory on the system partition, for which init will
3607 # create a symlink from /vendor to /system/vendor.
3608
3609 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003610if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3611 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003612 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003613 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3614 log -t recovery "Installing new recovery image: succeeded" || \\
3615 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003616else
3617 log -t recovery "Recovery image already installed"
3618fi
3619""" % {'type': recovery_type,
3620 'device': recovery_device,
3621 'sha1': recovery_img.sha1,
3622 'size': recovery_img.size}
3623 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003624 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003625if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3626 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003627 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003628 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3629 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3630 log -t recovery "Installing new recovery image: succeeded" || \\
3631 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003632else
3633 log -t recovery "Recovery image already installed"
3634fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003635""" % {'boot_size': boot_img.size,
3636 'boot_sha1': boot_img.sha1,
3637 'recovery_size': recovery_img.size,
3638 'recovery_sha1': recovery_img.sha1,
3639 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003640 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003641 'recovery_type': recovery_type,
3642 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003643 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003644
Bill Peckhame868aec2019-09-17 17:06:47 -07003645 # The install script location moved from /system/etc to /system/bin in the L
3646 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3647 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003648
Tao Bao32fcdab2018-10-12 10:30:39 -07003649 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003650
Tao Baoda30cfa2017-12-01 16:19:46 -08003651 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003652
3653
3654class DynamicPartitionUpdate(object):
3655 def __init__(self, src_group=None, tgt_group=None, progress=None,
3656 block_difference=None):
3657 self.src_group = src_group
3658 self.tgt_group = tgt_group
3659 self.progress = progress
3660 self.block_difference = block_difference
3661
3662 @property
3663 def src_size(self):
3664 if not self.block_difference:
3665 return 0
3666 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3667
3668 @property
3669 def tgt_size(self):
3670 if not self.block_difference:
3671 return 0
3672 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3673
3674 @staticmethod
3675 def _GetSparseImageSize(img):
3676 if not img:
3677 return 0
3678 return img.blocksize * img.total_blocks
3679
3680
3681class DynamicGroupUpdate(object):
3682 def __init__(self, src_size=None, tgt_size=None):
3683 # None: group does not exist. 0: no size limits.
3684 self.src_size = src_size
3685 self.tgt_size = tgt_size
3686
3687
3688class DynamicPartitionsDifference(object):
3689 def __init__(self, info_dict, block_diffs, progress_dict=None,
3690 source_info_dict=None):
3691 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003692 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003693
3694 self._remove_all_before_apply = False
3695 if source_info_dict is None:
3696 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003697 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003698
Tao Baof1113e92019-06-18 12:10:14 -07003699 block_diff_dict = collections.OrderedDict(
3700 [(e.partition, e) for e in block_diffs])
3701
Yifan Hong10c530d2018-12-27 17:34:18 -08003702 assert len(block_diff_dict) == len(block_diffs), \
3703 "Duplicated BlockDifference object for {}".format(
3704 [partition for partition, count in
3705 collections.Counter(e.partition for e in block_diffs).items()
3706 if count > 1])
3707
Yifan Hong79997e52019-01-23 16:56:19 -08003708 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003709
3710 for p, block_diff in block_diff_dict.items():
3711 self._partition_updates[p] = DynamicPartitionUpdate()
3712 self._partition_updates[p].block_difference = block_diff
3713
3714 for p, progress in progress_dict.items():
3715 if p in self._partition_updates:
3716 self._partition_updates[p].progress = progress
3717
3718 tgt_groups = shlex.split(info_dict.get(
3719 "super_partition_groups", "").strip())
3720 src_groups = shlex.split(source_info_dict.get(
3721 "super_partition_groups", "").strip())
3722
3723 for g in tgt_groups:
3724 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003725 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003726 assert p in self._partition_updates, \
3727 "{} is in target super_{}_partition_list but no BlockDifference " \
3728 "object is provided.".format(p, g)
3729 self._partition_updates[p].tgt_group = g
3730
3731 for g in src_groups:
3732 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003733 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003734 assert p in self._partition_updates, \
3735 "{} is in source super_{}_partition_list but no BlockDifference " \
3736 "object is provided.".format(p, g)
3737 self._partition_updates[p].src_group = g
3738
Yifan Hong45433e42019-01-18 13:55:25 -08003739 target_dynamic_partitions = set(shlex.split(info_dict.get(
3740 "dynamic_partition_list", "").strip()))
3741 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3742 if u.tgt_size)
3743 assert block_diffs_with_target == target_dynamic_partitions, \
3744 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3745 list(target_dynamic_partitions), list(block_diffs_with_target))
3746
3747 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3748 "dynamic_partition_list", "").strip()))
3749 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3750 if u.src_size)
3751 assert block_diffs_with_source == source_dynamic_partitions, \
3752 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3753 list(source_dynamic_partitions), list(block_diffs_with_source))
3754
Yifan Hong10c530d2018-12-27 17:34:18 -08003755 if self._partition_updates:
3756 logger.info("Updating dynamic partitions %s",
3757 self._partition_updates.keys())
3758
Yifan Hong79997e52019-01-23 16:56:19 -08003759 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003760
3761 for g in tgt_groups:
3762 self._group_updates[g] = DynamicGroupUpdate()
3763 self._group_updates[g].tgt_size = int(info_dict.get(
3764 "super_%s_group_size" % g, "0").strip())
3765
3766 for g in src_groups:
3767 if g not in self._group_updates:
3768 self._group_updates[g] = DynamicGroupUpdate()
3769 self._group_updates[g].src_size = int(source_info_dict.get(
3770 "super_%s_group_size" % g, "0").strip())
3771
3772 self._Compute()
3773
3774 def WriteScript(self, script, output_zip, write_verify_script=False):
3775 script.Comment('--- Start patching dynamic partitions ---')
3776 for p, u in self._partition_updates.items():
3777 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3778 script.Comment('Patch partition %s' % p)
3779 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3780 write_verify_script=False)
3781
3782 op_list_path = MakeTempFile()
3783 with open(op_list_path, 'w') as f:
3784 for line in self._op_list:
3785 f.write('{}\n'.format(line))
3786
3787 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3788
3789 script.Comment('Update dynamic partition metadata')
3790 script.AppendExtra('assert(update_dynamic_partitions('
3791 'package_extract_file("dynamic_partitions_op_list")));')
3792
3793 if write_verify_script:
3794 for p, u in self._partition_updates.items():
3795 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3796 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003797 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003798
3799 for p, u in self._partition_updates.items():
3800 if u.tgt_size and u.src_size <= u.tgt_size:
3801 script.Comment('Patch partition %s' % p)
3802 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3803 write_verify_script=write_verify_script)
3804 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003805 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003806
3807 script.Comment('--- End patching dynamic partitions ---')
3808
3809 def _Compute(self):
3810 self._op_list = list()
3811
3812 def append(line):
3813 self._op_list.append(line)
3814
3815 def comment(line):
3816 self._op_list.append("# %s" % line)
3817
3818 if self._remove_all_before_apply:
3819 comment('Remove all existing dynamic partitions and groups before '
3820 'applying full OTA')
3821 append('remove_all_groups')
3822
3823 for p, u in self._partition_updates.items():
3824 if u.src_group and not u.tgt_group:
3825 append('remove %s' % p)
3826
3827 for p, u in self._partition_updates.items():
3828 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3829 comment('Move partition %s from %s to default' % (p, u.src_group))
3830 append('move %s default' % p)
3831
3832 for p, u in self._partition_updates.items():
3833 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3834 comment('Shrink partition %s from %d to %d' %
3835 (p, u.src_size, u.tgt_size))
3836 append('resize %s %s' % (p, u.tgt_size))
3837
3838 for g, u in self._group_updates.items():
3839 if u.src_size is not None and u.tgt_size is None:
3840 append('remove_group %s' % g)
3841 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003842 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003843 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3844 append('resize_group %s %d' % (g, u.tgt_size))
3845
3846 for g, u in self._group_updates.items():
3847 if u.src_size is None and u.tgt_size is not None:
3848 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3849 append('add_group %s %d' % (g, u.tgt_size))
3850 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003851 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003852 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3853 append('resize_group %s %d' % (g, u.tgt_size))
3854
3855 for p, u in self._partition_updates.items():
3856 if u.tgt_group and not u.src_group:
3857 comment('Add partition %s to group %s' % (p, u.tgt_group))
3858 append('add %s %s' % (p, u.tgt_group))
3859
3860 for p, u in self._partition_updates.items():
3861 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003862 comment('Grow partition %s from %d to %d' %
3863 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003864 append('resize %s %d' % (p, u.tgt_size))
3865
3866 for p, u in self._partition_updates.items():
3867 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3868 comment('Move partition %s from default to %s' %
3869 (p, u.tgt_group))
3870 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003871
3872
jiajia tangf3f842b2021-03-17 21:49:44 +08003873def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003874 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003875 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003876
3877 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003878 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003879
3880 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003881 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003882 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003883 tmp_dir = MakeTempDir('boot_', suffix='.img')
3884 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003885 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3886 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003887 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3888 if not os.path.isfile(ramdisk):
3889 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3890 return None
3891 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003892 if ramdisk_format == RamdiskFormat.LZ4:
3893 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3894 elif ramdisk_format == RamdiskFormat.GZ:
3895 with open(ramdisk, 'rb') as input_stream:
3896 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003897 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3898 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003899 p2.wait()
3900 else:
3901 logger.error('Only support lz4 or minigzip ramdisk format.')
3902 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003903
3904 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3905 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3906 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3907 # the host environment.
3908 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003909 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003910
Yifan Hongc65a0542021-01-07 14:21:01 -08003911 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3912 prop_file = os.path.join(extracted_ramdisk, search_path)
3913 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003914 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003915 logger.warning(
3916 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003917
Yifan Hong7dc51172021-01-12 11:27:39 -08003918 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003919
Yifan Hong85ac5012021-01-07 14:43:46 -08003920 except ExternalError as e:
3921 logger.warning('Unable to get boot image build props: %s', e)
3922 return None
3923
3924
3925def GetBootImageTimestamp(boot_img):
3926 """
3927 Get timestamp from ramdisk within the boot image
3928
3929 Args:
3930 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3931
3932 Return:
3933 An integer that corresponds to the timestamp of the boot image, or None
3934 if file has unknown format. Raise exception if an unexpected error has
3935 occurred.
3936 """
3937 prop_file = GetBootImageBuildProp(boot_img)
3938 if not prop_file:
3939 return None
3940
3941 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3942 if props is None:
3943 return None
3944
3945 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003946 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3947 if timestamp:
3948 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003949 logger.warning(
3950 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003951 return None
3952
3953 except ExternalError as e:
3954 logger.warning('Unable to get boot image timestamp: %s', e)
3955 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003956
3957
3958def GetCareMap(which, imgname):
3959 """Returns the care_map string for the given partition.
3960
3961 Args:
3962 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3963 imgname: The filename of the image.
3964
3965 Returns:
3966 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3967 RangeSet; or None.
3968 """
3969 assert which in PARTITIONS_WITH_CARE_MAP
3970
3971 # which + "_image_size" contains the size that the actual filesystem image
3972 # resides in, which is all that needs to be verified. The additional blocks in
3973 # the image file contain verity metadata, by reading which would trigger
3974 # invalid reads.
3975 image_size = OPTIONS.info_dict.get(which + "_image_size")
3976 if not image_size:
3977 return None
3978
David Anderson9e95a022021-08-31 21:32:45 -07003979 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
3980
Kelvin Zhang27324132021-03-22 15:38:38 -04003981 image_blocks = int(image_size) // 4096 - 1
Kelvin Zhang98ef7bb2022-01-07 14:41:46 -08003982 # It's OK for image_blocks to be 0, because care map ranges are inclusive.
3983 # So 0-0 means "just block 0", which is valid.
3984 assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
3985 which, image_size)
Kelvin Zhang27324132021-03-22 15:38:38 -04003986
3987 # For sparse images, we will only check the blocks that are listed in the care
3988 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07003989 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04003990 simg = sparse_img.SparseImage(imgname)
3991 care_map_ranges = simg.care_map.intersect(
3992 rangelib.RangeSet("0-{}".format(image_blocks)))
3993
3994 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3995 # image.
3996 else:
3997 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3998
3999 return [which, care_map_ranges.to_string_raw()]
4000
4001
4002def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
4003 """Generates and adds care_map.pb for a/b partition that has care_map.
4004
4005 Args:
4006 output_file: The output zip file (needs to be already open),
4007 or file path to write care_map.pb.
4008 ab_partitions: The list of A/B partitions.
4009 image_paths: A map from the partition name to the image path.
4010 """
4011 if not output_file:
4012 raise ExternalError('Expected output_file for AddCareMapForAbOta')
4013
4014 care_map_list = []
4015 for partition in ab_partitions:
4016 partition = partition.strip()
4017 if partition not in PARTITIONS_WITH_CARE_MAP:
4018 continue
4019
4020 verity_block_device = "{}_verity_block_device".format(partition)
4021 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
4022 if (verity_block_device in OPTIONS.info_dict or
4023 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
4024 if partition not in image_paths:
4025 logger.warning('Potential partition with care_map missing from images: %s',
4026 partition)
4027 continue
4028 image_path = image_paths[partition]
4029 if not os.path.exists(image_path):
4030 raise ExternalError('Expected image at path {}'.format(image_path))
4031
4032 care_map = GetCareMap(partition, image_path)
4033 if not care_map:
4034 continue
4035 care_map_list += care_map
4036
4037 # adds fingerprint field to the care_map
4038 # TODO(xunchang) revisit the fingerprint calculation for care_map.
4039 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
4040 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
4041 "ro.{}.build.thumbprint".format(partition)]
4042
4043 present_props = [x for x in prop_name_list if
4044 partition_props and partition_props.GetProp(x)]
4045 if not present_props:
4046 logger.warning(
4047 "fingerprint is not present for partition %s", partition)
4048 property_id, fingerprint = "unknown", "unknown"
4049 else:
4050 property_id = present_props[0]
4051 fingerprint = partition_props.GetProp(property_id)
4052 care_map_list += [property_id, fingerprint]
4053
4054 if not care_map_list:
4055 return
4056
4057 # Converts the list into proto buf message by calling care_map_generator; and
4058 # writes the result to a temp file.
4059 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
4060 suffix=".txt")
4061 with open(temp_care_map_text, 'w') as text_file:
4062 text_file.write('\n'.join(care_map_list))
4063
4064 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
4065 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
4066 RunAndCheckOutput(care_map_gen_cmd)
4067
4068 if not isinstance(output_file, zipfile.ZipFile):
4069 shutil.copy(temp_care_map, output_file)
4070 return
4071 # output_file is a zip file
4072 care_map_path = "META/care_map.pb"
4073 if care_map_path in output_file.namelist():
4074 # Copy the temp file into the OPTIONS.input_tmp dir and update the
4075 # replace_updated_files_list used by add_img_to_target_files
4076 if not OPTIONS.replace_updated_files_list:
4077 OPTIONS.replace_updated_files_list = []
4078 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
4079 OPTIONS.replace_updated_files_list.append(care_map_path)
4080 else:
4081 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07004082
4083
4084def IsSparseImage(filepath):
4085 with open(filepath, 'rb') as fp:
4086 # Magic for android sparse image format
4087 # https://source.android.com/devices/bootloader/images
4088 return fp.read(4) == b'\x3A\xFF\x26\xED'