blob: 9013b37d43f48d39278c21c13331484245cd54a0 [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.
Sorin Basca64f17c12022-09-14 11:33:22 +010078 self.java_args = ["-Xmx4096m"] # 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",
Alexander Martinz2f52b052022-04-08 10:56:54 +0200371 "ro.product.name",
372 "ro.shift.sos.version.number",
373 "ro.shift.sos.version.extra",
374 "ro.shift.release.type"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700375 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
376 "product", "odm", "vendor", "system_ext", "system"]
377 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
378 "product", "product_services", "odm", "vendor", "system"]
379 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700380
Tianjiefdda51d2021-05-05 14:46:35 -0700381 # The length of vbmeta digest to append to the fingerprint
382 _VBMETA_DIGEST_SIZE_USED = 8
383
384 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700385 """Initializes a BuildInfo instance with the given dicts.
386
387 Note that it only wraps up the given dicts, without making copies.
388
389 Arguments:
390 info_dict: The build-time info dict.
391 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
392 that it always uses the first dict to calculate the fingerprint or the
393 device name. The rest would be used for asserting OEM properties only
394 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700395 use_legacy_id: Use the legacy build id to construct the fingerprint. This
396 is used when we need a BuildInfo class, while the vbmeta digest is
397 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700398
399 Raises:
400 ValueError: On invalid inputs.
401 """
402 self.info_dict = info_dict
403 self.oem_dicts = oem_dicts
404
405 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700406 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700407
Hongguang Chend7c160f2020-05-03 21:24:26 -0700408 # Skip _oem_props if oem_dicts is None to use BuildInfo in
409 # sign_target_files_apks
410 if self.oem_dicts:
411 self._oem_props = info_dict.get("oem_fingerprint_properties")
412 else:
413 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700414
Daniel Normand5fe8622020-01-08 17:01:11 -0800415 def check_fingerprint(fingerprint):
416 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
417 raise ValueError(
418 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
419 "3.2.2. Build Parameters.".format(fingerprint))
420
Daniel Normand5fe8622020-01-08 17:01:11 -0800421 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800422 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800423 try:
424 fingerprint = self.CalculatePartitionFingerprint(partition)
425 check_fingerprint(fingerprint)
426 self._partition_fingerprints[partition] = fingerprint
427 except ExternalError:
428 continue
429 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800430 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800431 # need a fingerprint when creating the image.
432 self._partition_fingerprints[
433 "system_other"] = self._partition_fingerprints["system"]
434
Tao Bao1c320f82019-10-04 23:25:12 -0700435 # These two should be computed only after setting self._oem_props.
Steve Kondik4b8a85a2010-04-21 11:39:48 -0400436 self._device = info_dict.get("ota_override_device", self.GetOemProperty("ro.product.device"))
Tao Bao1c320f82019-10-04 23:25:12 -0700437 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800438 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700439
440 @property
441 def is_ab(self):
442 return self._is_ab
443
444 @property
445 def device(self):
446 return self._device
447
448 @property
449 def fingerprint(self):
450 return self._fingerprint
451
452 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400453 def is_vabc(self):
454 vendor_prop = self.info_dict.get("vendor.build.prop")
455 vabc_enabled = vendor_prop and \
456 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
457 return vabc_enabled
458
459 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700460 def is_vabc_xor(self):
461 vendor_prop = self.info_dict.get("vendor.build.prop")
462 vabc_xor_enabled = vendor_prop and \
463 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
464 return vabc_xor_enabled
465
466 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400467 def vendor_suppressed_vabc(self):
468 vendor_prop = self.info_dict.get("vendor.build.prop")
469 vabc_suppressed = vendor_prop and \
470 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
471 return vabc_suppressed and vabc_suppressed.lower() == "true"
472
473 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700474 def oem_props(self):
475 return self._oem_props
476
477 def __getitem__(self, key):
478 return self.info_dict[key]
479
480 def __setitem__(self, key, value):
481 self.info_dict[key] = value
482
483 def get(self, key, default=None):
484 return self.info_dict.get(key, default)
485
486 def items(self):
487 return self.info_dict.items()
488
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 def _GetRawBuildProp(self, prop, partition):
490 prop_file = '{}.build.prop'.format(
491 partition) if partition else 'build.prop'
492 partition_props = self.info_dict.get(prop_file)
493 if not partition_props:
494 return None
495 return partition_props.GetProp(prop)
496
Daniel Normand5fe8622020-01-08 17:01:11 -0800497 def GetPartitionBuildProp(self, prop, partition):
498 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800499
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000500 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000501 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000502 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800503
Daniel Normand5fe8622020-01-08 17:01:11 -0800504 # If provided a partition for this property, only look within that
505 # partition's build.prop.
506 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800507 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800508 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800509 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000510
511 prop_val = self._GetRawBuildProp(prop, partition)
512 if prop_val is not None:
513 return prop_val
514 raise ExternalError("couldn't find %s in %s.build.prop" %
515 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800516
Tao Bao1c320f82019-10-04 23:25:12 -0700517 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800518 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700519 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
520 return self._ResolveRoProductBuildProp(prop)
521
Tianjiefdda51d2021-05-05 14:46:35 -0700522 if prop == "ro.build.id":
523 return self._GetBuildId()
524
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000525 prop_val = self._GetRawBuildProp(prop, None)
526 if prop_val is not None:
527 return prop_val
528
529 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700530
531 def _ResolveRoProductBuildProp(self, prop):
532 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000533 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700534 if prop_val:
535 return prop_val
536
Steven Laver8e2086e2020-04-27 16:26:31 -0700537 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000538 source_order_val = self._GetRawBuildProp(
539 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700540 if source_order_val:
541 source_order = source_order_val.split(",")
542 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700543 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700544
545 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700546 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700547 raise ExternalError(
548 "Invalid ro.product.property_source_order '{}'".format(source_order))
549
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000550 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700551 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000552 "ro.product", "ro.product.{}".format(source_partition), 1)
553 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700554 if prop_val:
555 return prop_val
556
557 raise ExternalError("couldn't resolve {}".format(prop))
558
Steven Laver8e2086e2020-04-27 16:26:31 -0700559 def _GetRoProductPropsDefaultSourceOrder(self):
560 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
561 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000562 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700563 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000564 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700565 if android_version == "10":
566 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
567 # NOTE: float() conversion of android_version will have rounding error.
568 # We are checking for "9" or less, and using "< 10" is well outside of
569 # possible floating point rounding.
570 try:
571 android_version_val = float(android_version)
572 except ValueError:
573 android_version_val = 0
574 if android_version_val < 10:
575 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
576 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
577
Tianjieb37c5be2020-10-15 21:27:10 -0700578 def _GetPlatformVersion(self):
579 version_sdk = self.GetBuildProp("ro.build.version.sdk")
580 # init code switches to version_release_or_codename (see b/158483506). After
581 # API finalization, release_or_codename will be the same as release. This
582 # is the best effort to support pre-S dev stage builds.
583 if int(version_sdk) >= 30:
584 try:
585 return self.GetBuildProp("ro.build.version.release_or_codename")
586 except ExternalError:
587 logger.warning('Failed to find ro.build.version.release_or_codename')
588
589 return self.GetBuildProp("ro.build.version.release")
590
Tianjiefdda51d2021-05-05 14:46:35 -0700591 def _GetBuildId(self):
592 build_id = self._GetRawBuildProp("ro.build.id", None)
593 if build_id:
594 return build_id
595
596 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
597 if not legacy_build_id:
598 raise ExternalError("Couldn't find build id in property file")
599
600 if self.use_legacy_id:
601 return legacy_build_id
602
603 # Append the top 8 chars of vbmeta digest to the existing build id. The
604 # logic needs to match the one in init, so that OTA can deliver correctly.
605 avb_enable = self.info_dict.get("avb_enable") == "true"
606 if not avb_enable:
607 raise ExternalError("AVB isn't enabled when using legacy build id")
608
609 vbmeta_digest = self.info_dict.get("vbmeta_digest")
610 if not vbmeta_digest:
611 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
612 " id")
613 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
614 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
615
616 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
617 return legacy_build_id + '.' + digest_prefix
618
Tianjieb37c5be2020-10-15 21:27:10 -0700619 def _GetPartitionPlatformVersion(self, partition):
620 try:
621 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
622 partition)
623 except ExternalError:
624 return self.GetPartitionBuildProp("ro.build.version.release",
625 partition)
626
Tao Bao1c320f82019-10-04 23:25:12 -0700627 def GetOemProperty(self, key):
628 if self.oem_props is not None and key in self.oem_props:
629 return self.oem_dicts[0][key]
630 return self.GetBuildProp(key)
631
Daniel Normand5fe8622020-01-08 17:01:11 -0800632 def GetPartitionFingerprint(self, partition):
Aaron Kling088a3ef2021-02-17 14:10:32 -0600633 return self._partition_fingerprints.get(partition, self.CalculateFingerprint())
Daniel Normand5fe8622020-01-08 17:01:11 -0800634
635 def CalculatePartitionFingerprint(self, partition):
636 try:
637 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
638 except ExternalError:
639 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
640 self.GetPartitionBuildProp("ro.product.brand", partition),
641 self.GetPartitionBuildProp("ro.product.name", partition),
642 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700643 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800644 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400645 self.GetPartitionBuildProp(
646 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800647 self.GetPartitionBuildProp("ro.build.type", partition),
648 self.GetPartitionBuildProp("ro.build.tags", partition))
649
Tao Bao1c320f82019-10-04 23:25:12 -0700650 def CalculateFingerprint(self):
651 if self.oem_props is None:
652 try:
653 return self.GetBuildProp("ro.build.fingerprint")
654 except ExternalError:
655 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
656 self.GetBuildProp("ro.product.brand"),
657 self.GetBuildProp("ro.product.name"),
658 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700659 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700660 self.GetBuildProp("ro.build.id"),
661 self.GetBuildProp("ro.build.version.incremental"),
662 self.GetBuildProp("ro.build.type"),
663 self.GetBuildProp("ro.build.tags"))
664 return "%s/%s/%s:%s" % (
665 self.GetOemProperty("ro.product.brand"),
666 self.GetOemProperty("ro.product.name"),
667 self.GetOemProperty("ro.product.device"),
668 self.GetBuildProp("ro.build.thumbprint"))
669
670 def WriteMountOemScript(self, script):
671 assert self.oem_props is not None
672 recovery_mount_options = self.info_dict.get("recovery_mount_options")
673 script.Mount("/oem", recovery_mount_options)
674
675 def WriteDeviceAssertions(self, script, oem_no_mount):
676 # Read the property directly if not using OEM properties.
677 if not self.oem_props:
678 script.AssertDevice(self.device)
679 return
680
681 # Otherwise assert OEM properties.
682 if not self.oem_dicts:
683 raise ExternalError(
684 "No OEM file provided to answer expected assertions")
685
686 for prop in self.oem_props.split():
687 values = []
688 for oem_dict in self.oem_dicts:
689 if prop in oem_dict:
690 values.append(oem_dict[prop])
691 if not values:
692 raise ExternalError(
693 "The OEM file is missing the property %s" % (prop,))
694 script.AssertOemProperty(prop, values, oem_no_mount)
695
696
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000697def ReadFromInputFile(input_file, fn):
698 """Reads the contents of fn from input zipfile or directory."""
699 if isinstance(input_file, zipfile.ZipFile):
700 return input_file.read(fn).decode()
701 else:
702 path = os.path.join(input_file, *fn.split("/"))
703 try:
704 with open(path) as f:
705 return f.read()
706 except IOError as e:
707 if e.errno == errno.ENOENT:
708 raise KeyError(fn)
709
710
Yifan Hong10482a22021-01-07 14:38:41 -0800711def ExtractFromInputFile(input_file, fn):
712 """Extracts the contents of fn from input zipfile or directory into a file."""
713 if isinstance(input_file, zipfile.ZipFile):
714 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500715 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800716 f.write(input_file.read(fn))
717 return tmp_file
718 else:
719 file = os.path.join(input_file, *fn.split("/"))
720 if not os.path.exists(file):
721 raise KeyError(fn)
722 return file
723
Kelvin Zhang563750f2021-04-28 12:46:17 -0400724
jiajia tangf3f842b2021-03-17 21:49:44 +0800725class RamdiskFormat(object):
726 LZ4 = 1
727 GZ = 2
Luca Stefani7f2913c2020-06-11 13:03:18 +0200728 XZ = 3
Yifan Hong10482a22021-01-07 14:38:41 -0800729
Kelvin Zhang563750f2021-04-28 12:46:17 -0400730
jiajia tang836f76b2021-04-02 14:48:26 +0800731def _GetRamdiskFormat(info_dict):
732 if info_dict.get('lz4_ramdisks') == 'true':
733 ramdisk_format = RamdiskFormat.LZ4
pjgowtham7e3992a2022-11-18 02:43:17 +0530734 elif info_dict.get('xz_ramdisks') == 'true':
Luca Stefani7f2913c2020-06-11 13:03:18 +0200735 ramdisk_format = RamdiskFormat.XZ
jiajia tang836f76b2021-04-02 14:48:26 +0800736 else:
737 ramdisk_format = RamdiskFormat.GZ
738 return ramdisk_format
739
Kelvin Zhang563750f2021-04-28 12:46:17 -0400740
Tao Bao410ad8b2018-08-24 12:08:38 -0700741def LoadInfoDict(input_file, repacking=False):
742 """Loads the key/value pairs from the given input target_files.
743
Tianjiea85bdf02020-07-29 11:56:19 -0700744 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700745 checks and returns the parsed key/value pairs for to the given build. It's
746 usually called early when working on input target_files files, e.g. when
747 generating OTAs, or signing builds. Note that the function may be called
748 against an old target_files file (i.e. from past dessert releases). So the
749 property parsing needs to be backward compatible.
750
751 In a `META/misc_info.txt`, a few properties are stored as links to the files
752 in the PRODUCT_OUT directory. It works fine with the build system. However,
753 they are no longer available when (re)generating images from target_files zip.
754 When `repacking` is True, redirect these properties to the actual files in the
755 unzipped directory.
756
757 Args:
758 input_file: The input target_files file, which could be an open
759 zipfile.ZipFile instance, or a str for the dir that contains the files
760 unzipped from a target_files file.
761 repacking: Whether it's trying repack an target_files file after loading the
762 info dict (default: False). If so, it will rewrite a few loaded
763 properties (e.g. selinux_fc, root_dir) to point to the actual files in
764 target_files file. When doing repacking, `input_file` must be a dir.
765
766 Returns:
767 A dict that contains the parsed key/value pairs.
768
769 Raises:
770 AssertionError: On invalid input arguments.
771 ValueError: On malformed input values.
772 """
773 if repacking:
774 assert isinstance(input_file, str), \
775 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700776
Doug Zongkerc9253822014-02-04 12:17:58 -0800777 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000778 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800779
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700780 try:
Michael Runge6e836112014-04-15 17:40:21 -0700781 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700782 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700783 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700784
Tao Bao410ad8b2018-08-24 12:08:38 -0700785 if "recovery_api_version" not in d:
786 raise ValueError("Failed to find 'recovery_api_version'")
787 if "fstab_version" not in d:
788 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800789
Tao Bao410ad8b2018-08-24 12:08:38 -0700790 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700791 # "selinux_fc" properties should point to the file_contexts files
792 # (file_contexts.bin) under META/.
793 for key in d:
794 if key.endswith("selinux_fc"):
795 fc_basename = os.path.basename(d[key])
796 fc_config = os.path.join(input_file, "META", fc_basename)
797 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700798
Daniel Norman72c626f2019-05-13 15:58:14 -0700799 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700800
Tom Cherryd14b8952018-08-09 14:26:00 -0700801 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700802 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700803 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700804 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700805
David Anderson0ec64ac2019-12-06 12:21:18 -0800806 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700807 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000808 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800809 key_name = part_name + "_base_fs_file"
810 if key_name not in d:
811 continue
812 basename = os.path.basename(d[key_name])
813 base_fs_file = os.path.join(input_file, "META", basename)
814 if os.path.exists(base_fs_file):
815 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700816 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700817 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800818 "Failed to find %s base fs file: %s", part_name, base_fs_file)
819 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700820
Doug Zongker37974732010-09-16 17:44:38 -0700821 def makeint(key):
822 if key in d:
823 d[key] = int(d[key], 0)
824
825 makeint("recovery_api_version")
826 makeint("blocksize")
827 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700828 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700829 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700830 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700831 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800832 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700833
Steve Muckle903a1ca2020-05-07 17:32:10 -0700834 boot_images = "boot.img"
835 if "boot_images" in d:
836 boot_images = d["boot_images"]
837 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400838 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700839
Tao Bao765668f2019-10-04 22:03:00 -0700840 # Load recovery fstab if applicable.
841 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800842 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800843
Tianjie Xu861f4132018-09-12 11:49:33 -0700844 # Tries to load the build props for all partitions with care_map, including
845 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800846 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800847 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000848 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800849 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700850 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800851
Tao Bao3ed35d32019-10-07 20:48:48 -0700852 # Set up the salt (based on fingerprint) that will be used when adding AVB
853 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800854 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700855 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800856 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800857 fingerprint = build_info.GetPartitionFingerprint(partition)
858 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400859 d["avb_{}_salt".format(partition)] = sha256(
860 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700861
862 # Set the vbmeta digest if exists
863 try:
864 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
865 except KeyError:
866 pass
867
Kelvin Zhang39aea442020-08-17 11:04:25 -0400868 try:
869 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
870 except KeyError:
871 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700872 return d
873
Tao Baod1de6f32017-03-01 16:38:48 -0800874
Daniel Norman4cc9df62019-07-18 10:11:07 -0700875def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900876 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700877 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900878
Daniel Norman4cc9df62019-07-18 10:11:07 -0700879
880def LoadDictionaryFromFile(file_path):
881 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900882 return LoadDictionaryFromLines(lines)
883
884
Michael Runge6e836112014-04-15 17:40:21 -0700885def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700886 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700887 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700888 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700889 if not line or line.startswith("#"):
890 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700891 if "=" in line:
892 name, value = line.split("=", 1)
893 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700894 return d
895
Tao Baod1de6f32017-03-01 16:38:48 -0800896
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000897class PartitionBuildProps(object):
898 """The class holds the build prop of a particular partition.
899
900 This class loads the build.prop and holds the build properties for a given
901 partition. It also partially recognizes the 'import' statement in the
902 build.prop; and calculates alternative values of some specific build
903 properties during runtime.
904
905 Attributes:
906 input_file: a zipped target-file or an unzipped target-file directory.
907 partition: name of the partition.
908 props_allow_override: a list of build properties to search for the
909 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000910 build_props: a dict of build properties for the given partition.
911 prop_overrides: a set of props that are overridden by import.
912 placeholder_values: A dict of runtime variables' values to replace the
913 placeholders in the build.prop file. We expect exactly one value for
914 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800915 ramdisk_format: If name is "boot", the format of ramdisk inside the
916 boot image. Otherwise, its value is ignored.
917 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000918 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400919
Tianjie Xu9afb2212020-05-10 21:48:15 +0000920 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000921 self.input_file = input_file
922 self.partition = name
923 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000924 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000925 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 self.prop_overrides = set()
927 self.placeholder_values = {}
928 if placeholder_values:
929 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000930
931 @staticmethod
932 def FromDictionary(name, build_props):
933 """Constructs an instance from a build prop dictionary."""
934
935 props = PartitionBuildProps("unknown", name)
936 props.build_props = build_props.copy()
937 return props
938
939 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800940 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000941 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800942
Devin Mooreafdd7c72021-12-13 22:04:08 +0000943 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400944 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000945 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800946 else:
947 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
948
949 props = PartitionBuildProps(input_file, name, placeholder_values)
950 props._LoadBuildProp(data)
951 return props
952
953 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000954 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800955 """
956 Read build.prop for boot image from input_file.
957 Return empty string if not found.
958 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000959 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800960 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000961 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800962 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000963 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800964 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800965 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800966 if prop_file is None:
967 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500968 with open(prop_file, "r") as f:
969 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800970
971 @staticmethod
972 def _ReadPartitionPropFile(input_file, name):
973 """
974 Read build.prop for name from input_file.
975 Return empty string if not found.
976 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000977 data = ''
978 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
979 '{}/build.prop'.format(name.upper())]:
980 try:
981 data = ReadFromInputFile(input_file, prop_file)
982 break
983 except KeyError:
984 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800985 if data == '':
986 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800987 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000988
Yifan Hong125d0b62020-09-24 17:07:03 -0700989 @staticmethod
990 def FromBuildPropFile(name, build_prop_file):
991 """Constructs an instance from a build prop file."""
992
993 props = PartitionBuildProps("unknown", name)
994 with open(build_prop_file) as f:
995 props._LoadBuildProp(f.read())
996 return props
997
Tianjie Xu9afb2212020-05-10 21:48:15 +0000998 def _LoadBuildProp(self, data):
999 for line in data.split('\n'):
1000 line = line.strip()
1001 if not line or line.startswith("#"):
1002 continue
1003 if line.startswith("import"):
1004 overrides = self._ImportParser(line)
1005 duplicates = self.prop_overrides.intersection(overrides.keys())
1006 if duplicates:
1007 raise ValueError('prop {} is overridden multiple times'.format(
1008 ','.join(duplicates)))
1009 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1010 self.build_props.update(overrides)
1011 elif "=" in line:
1012 name, value = line.split("=", 1)
1013 if name in self.prop_overrides:
1014 raise ValueError('prop {} is set again after overridden by import '
1015 'statement'.format(name))
1016 self.build_props[name] = value
1017
1018 def _ImportParser(self, line):
1019 """Parses the build prop in a given import statement."""
1020
1021 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001022 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001023 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001024
1025 if len(tokens) == 3:
1026 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1027 return {}
1028
Tianjie Xu9afb2212020-05-10 21:48:15 +00001029 import_path = tokens[1]
1030 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001031 logger.warn('Unrecognized import path {}'.format(line))
1032 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001033
1034 # We only recognize a subset of import statement that the init process
1035 # supports. And we can loose the restriction based on how the dynamic
1036 # fingerprint is used in practice. The placeholder format should be
1037 # ${placeholder}, and its value should be provided by the caller through
1038 # the placeholder_values.
1039 for prop, value in self.placeholder_values.items():
1040 prop_place_holder = '${{{}}}'.format(prop)
1041 if prop_place_holder in import_path:
1042 import_path = import_path.replace(prop_place_holder, value)
1043 if '$' in import_path:
1044 logger.info('Unresolved place holder in import path %s', import_path)
1045 return {}
1046
1047 import_path = import_path.replace('/{}'.format(self.partition),
1048 self.partition.upper())
1049 logger.info('Parsing build props override from %s', import_path)
1050
1051 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1052 d = LoadDictionaryFromLines(lines)
1053 return {key: val for key, val in d.items()
1054 if key in self.props_allow_override}
1055
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001056 def GetProp(self, prop):
1057 return self.build_props.get(prop)
1058
1059
Tianjie Xucfa86222016-03-07 16:31:19 -08001060def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1061 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001062 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001063 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001064 self.mount_point = mount_point
1065 self.fs_type = fs_type
1066 self.device = device
1067 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001068 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001069 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001070
1071 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001072 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001073 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001074 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001075 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001076
Tao Baod1de6f32017-03-01 16:38:48 -08001077 assert fstab_version == 2
1078
1079 d = {}
1080 for line in data.split("\n"):
1081 line = line.strip()
1082 if not line or line.startswith("#"):
1083 continue
1084
1085 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1086 pieces = line.split()
1087 if len(pieces) != 5:
1088 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1089
1090 # Ignore entries that are managed by vold.
1091 options = pieces[4]
1092 if "voldmanaged=" in options:
1093 continue
1094
1095 # It's a good line, parse it.
1096 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001097 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001098 options = options.split(",")
1099 for i in options:
1100 if i.startswith("length="):
1101 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001102 elif i == "slotselect":
1103 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001104 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001105 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001106 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001107
Tao Baod1de6f32017-03-01 16:38:48 -08001108 mount_flags = pieces[3]
1109 # Honor the SELinux context if present.
1110 context = None
1111 for i in mount_flags.split(","):
1112 if i.startswith("context="):
1113 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001114
Tao Baod1de6f32017-03-01 16:38:48 -08001115 mount_point = pieces[1]
Brint E. Kriebel78222aa2018-02-14 23:02:06 -08001116 if not d.get(mount_point):
1117 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
1118 device=pieces[0], length=length, context=context,
1119 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001120
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001121 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001122 # system. Other areas assume system is always at "/system" so point /system
1123 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001124 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001125 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001126 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001127 return d
1128
1129
Tao Bao765668f2019-10-04 22:03:00 -07001130def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1131 """Finds the path to recovery fstab and loads its contents."""
1132 # recovery fstab is only meaningful when installing an update via recovery
1133 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001134 if info_dict.get('ab_update') == 'true' and \
1135 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001136 return None
1137
1138 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1139 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1140 # cases, since it may load the info_dict from an old build (e.g. when
1141 # generating incremental OTAs from that build).
1142 system_root_image = info_dict.get('system_root_image') == 'true'
1143 if info_dict.get('no_recovery') != 'true':
1144 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1145 if isinstance(input_file, zipfile.ZipFile):
1146 if recovery_fstab_path not in input_file.namelist():
1147 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1148 else:
1149 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1150 if not os.path.exists(path):
1151 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1152 return LoadRecoveryFSTab(
1153 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1154 system_root_image)
1155
1156 if info_dict.get('recovery_as_boot') == 'true':
1157 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1158 if isinstance(input_file, zipfile.ZipFile):
1159 if recovery_fstab_path not in input_file.namelist():
1160 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1161 else:
1162 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1163 if not os.path.exists(path):
1164 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1165 return LoadRecoveryFSTab(
1166 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1167 system_root_image)
1168
1169 return None
1170
1171
Doug Zongker37974732010-09-16 17:44:38 -07001172def DumpInfoDict(d):
1173 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001174 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001175
Dan Albert8b72aef2015-03-23 19:13:21 -07001176
Daniel Norman55417142019-11-25 16:04:36 -08001177def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001178 """Merges dynamic partition info variables.
1179
1180 Args:
1181 framework_dict: The dictionary of dynamic partition info variables from the
1182 partial framework target files.
1183 vendor_dict: The dictionary of dynamic partition info variables from the
1184 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001185
1186 Returns:
1187 The merged dynamic partition info dictionary.
1188 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001189
1190 def uniq_concat(a, b):
1191 combined = set(a.split(" "))
1192 combined.update(set(b.split(" ")))
1193 combined = [item.strip() for item in combined if item.strip()]
1194 return " ".join(sorted(combined))
1195
1196 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf4406ca2022-05-02 12:19:45 -07001197 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001198 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1199
1200 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhangf4406ca2022-05-02 12:19:45 -07001201 # For keys-value pairs that are the same, copy to merged dict
1202 for key in vendor_dict.keys():
1203 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1204 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001205
1206 merged_dict["dynamic_partition_list"] = uniq_concat(
1207 framework_dict.get("dynamic_partition_list", ""),
1208 vendor_dict.get("dynamic_partition_list", ""))
1209
1210 # Super block devices are defined by the vendor dict.
1211 if "super_block_devices" in vendor_dict:
1212 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1213 for block_device in merged_dict["super_block_devices"].split(" "):
1214 key = "super_%s_device_size" % block_device
1215 if key not in vendor_dict:
1216 raise ValueError("Vendor dict does not contain required key %s." % key)
1217 merged_dict[key] = vendor_dict[key]
1218
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001219 # Partition groups and group sizes are defined by the vendor dict because
1220 # these values may vary for each board that uses a shared system image.
1221 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001222 for partition_group in merged_dict["super_partition_groups"].split(" "):
1223 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001224 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001225 if key not in vendor_dict:
1226 raise ValueError("Vendor dict does not contain required key %s." % key)
1227 merged_dict[key] = vendor_dict[key]
1228
1229 # Set the partition group's partition list using a concatenation of the
1230 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001231 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001232 merged_dict[key] = uniq_concat(
1233 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301234
Daniel Normanb0c75912020-09-24 14:30:21 -07001235 # Various other flags should be copied from the vendor dict, if defined.
1236 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1237 "super_metadata_device", "super_partition_error_limit",
1238 "super_partition_size"):
1239 if key in vendor_dict.keys():
1240 merged_dict[key] = vendor_dict[key]
1241
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001242 return merged_dict
1243
1244
Daniel Norman21c34f72020-11-11 17:25:50 -08001245def PartitionMapFromTargetFiles(target_files_dir):
1246 """Builds a map from partition -> path within an extracted target files directory."""
1247 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1248 possible_subdirs = {
1249 "system": ["SYSTEM"],
1250 "vendor": ["VENDOR", "SYSTEM/vendor"],
1251 "product": ["PRODUCT", "SYSTEM/product"],
1252 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1253 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1254 "vendor_dlkm": [
1255 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1256 ],
1257 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001258 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001259 }
1260 partition_map = {}
1261 for partition, subdirs in possible_subdirs.items():
1262 for subdir in subdirs:
1263 if os.path.exists(os.path.join(target_files_dir, subdir)):
1264 partition_map[partition] = subdir
1265 break
1266 return partition_map
1267
1268
Daniel Normand3351562020-10-29 12:33:11 -07001269def SharedUidPartitionViolations(uid_dict, partition_groups):
1270 """Checks for APK sharedUserIds that cross partition group boundaries.
1271
1272 This uses a single or merged build's shareduid_violation_modules.json
1273 output file, as generated by find_shareduid_violation.py or
1274 core/tasks/find-shareduid-violation.mk.
1275
1276 An error is defined as a sharedUserId that is found in a set of partitions
1277 that span more than one partition group.
1278
1279 Args:
1280 uid_dict: A dictionary created by using the standard json module to read a
1281 complete shareduid_violation_modules.json file.
1282 partition_groups: A list of groups, where each group is a list of
1283 partitions.
1284
1285 Returns:
1286 A list of error messages.
1287 """
1288 errors = []
1289 for uid, partitions in uid_dict.items():
1290 found_in_groups = [
1291 group for group in partition_groups
1292 if set(partitions.keys()) & set(group)
1293 ]
1294 if len(found_in_groups) > 1:
1295 errors.append(
1296 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1297 % (uid, ",".join(sorted(partitions.keys()))))
1298 return errors
1299
1300
Daniel Norman21c34f72020-11-11 17:25:50 -08001301def RunHostInitVerifier(product_out, partition_map):
1302 """Runs host_init_verifier on the init rc files within partitions.
1303
1304 host_init_verifier searches the etc/init path within each partition.
1305
1306 Args:
1307 product_out: PRODUCT_OUT directory, containing partition directories.
1308 partition_map: A map of partition name -> relative path within product_out.
1309 """
1310 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1311 cmd = ["host_init_verifier"]
1312 for partition, path in partition_map.items():
1313 if partition not in allowed_partitions:
1314 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1315 partition)
1316 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1317 # Add --property-contexts if the file exists on the partition.
1318 property_contexts = "%s_property_contexts" % (
1319 "plat" if partition == "system" else partition)
1320 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1321 property_contexts)
1322 if os.path.exists(property_contexts_path):
1323 cmd.append("--property-contexts=%s" % property_contexts_path)
1324 # Add the passwd file if the file exists on the partition.
1325 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1326 if os.path.exists(passwd_path):
1327 cmd.extend(["-p", passwd_path])
1328 return RunAndCheckOutput(cmd)
1329
1330
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001331def AppendAVBSigningArgs(cmd, partition):
1332 """Append signing arguments for avbtool."""
1333 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1334 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001335 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1336 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1337 if os.path.exists(new_key_path):
1338 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001339 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1340 if key_path and algorithm:
1341 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001342 avb_salt = OPTIONS.info_dict.get("avb_salt")
1343 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001344 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001345 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001346
1347
Tao Bao765668f2019-10-04 22:03:00 -07001348def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001349 """Returns the VBMeta arguments for partition.
1350
1351 It sets up the VBMeta argument by including the partition descriptor from the
1352 given 'image', or by configuring the partition as a chained partition.
1353
1354 Args:
1355 partition: The name of the partition (e.g. "system").
1356 image: The path to the partition image.
1357 info_dict: A dict returned by common.LoadInfoDict(). Will use
1358 OPTIONS.info_dict if None has been given.
1359
1360 Returns:
1361 A list of VBMeta arguments.
1362 """
1363 if info_dict is None:
1364 info_dict = OPTIONS.info_dict
1365
1366 # Check if chain partition is used.
1367 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001368 if not key_path:
1369 return ["--include_descriptors_from_image", image]
1370
1371 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1372 # into vbmeta.img. The recovery image will be configured on an independent
1373 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1374 # See details at
1375 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001376 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001377 return []
1378
1379 # Otherwise chain the partition into vbmeta.
1380 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1381 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001382
1383
Tao Bao02a08592018-07-22 12:40:45 -07001384def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1385 """Constructs and returns the arg to build or verify a chained partition.
1386
1387 Args:
1388 partition: The partition name.
1389 info_dict: The info dict to look up the key info and rollback index
1390 location.
1391 key: The key to be used for building or verifying the partition. Defaults to
1392 the key listed in info_dict.
1393
1394 Returns:
1395 A string of form "partition:rollback_index_location:key" that can be used to
1396 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001397 """
1398 if key is None:
1399 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001400 if key and not os.path.exists(key) and OPTIONS.search_path:
1401 new_key_path = os.path.join(OPTIONS.search_path, key)
1402 if os.path.exists(new_key_path):
1403 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001404 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001405 rollback_index_location = info_dict[
1406 "avb_" + partition + "_rollback_index_location"]
1407 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1408
1409
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001410def _HasGkiCertificationArgs():
1411 return ("gki_signing_key_path" in OPTIONS.info_dict and
1412 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001413
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001414
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001415def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001416 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001417 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001418
1419 if not os.path.exists(key_path) and OPTIONS.search_path:
1420 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1421 if os.path.exists(new_key_path):
1422 key_path = new_key_path
1423
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001424 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001425 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001426 raise ExternalError(
1427 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001428
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001429 output_certificate = tempfile.NamedTemporaryFile()
1430 cmd = [
1431 "generate_gki_certificate",
1432 "--name", image_name,
1433 "--algorithm", algorithm,
1434 "--key", key_path,
1435 "--output", output_certificate.name,
1436 image,
1437 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001438
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001439 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1440 signature_args = signature_args.strip()
1441 if signature_args:
1442 cmd.extend(["--additional_avb_args", signature_args])
1443
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001444 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001445 args = args.strip()
1446 if args:
1447 cmd.extend(["--additional_avb_args", args])
1448
1449 RunAndCheckOutput(cmd)
1450
1451 output_certificate.seek(os.SEEK_SET, 0)
1452 data = output_certificate.read()
1453 output_certificate.close()
1454 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001455
1456
Daniel Norman276f0622019-07-26 14:13:51 -07001457def BuildVBMeta(image_path, partitions, name, needed_partitions):
1458 """Creates a VBMeta image.
1459
1460 It generates the requested VBMeta image. The requested image could be for
1461 top-level or chained VBMeta image, which is determined based on the name.
1462
1463 Args:
1464 image_path: The output path for the new VBMeta image.
1465 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001466 values. Only valid partition names are accepted, as partitions listed
1467 in common.AVB_PARTITIONS and custom partitions listed in
1468 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001469 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1470 needed_partitions: Partitions whose descriptors should be included into the
1471 generated VBMeta image.
1472
1473 Raises:
1474 AssertionError: On invalid input args.
1475 """
1476 avbtool = OPTIONS.info_dict["avb_avbtool"]
1477 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1478 AppendAVBSigningArgs(cmd, name)
1479
Hongguang Chenf23364d2020-04-27 18:36:36 -07001480 custom_partitions = OPTIONS.info_dict.get(
1481 "avb_custom_images_partition_list", "").strip().split()
1482
Daniel Norman276f0622019-07-26 14:13:51 -07001483 for partition, path in partitions.items():
1484 if partition not in needed_partitions:
1485 continue
1486 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001487 partition in AVB_VBMETA_PARTITIONS or
1488 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001489 'Unknown partition: {}'.format(partition)
1490 assert os.path.exists(path), \
1491 'Failed to find {} for {}'.format(path, partition)
1492 cmd.extend(GetAvbPartitionArg(partition, path))
1493
1494 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1495 if args and args.strip():
1496 split_args = shlex.split(args)
1497 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001498 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001499 # as a path relative to source tree, which may not be available at the
1500 # same location when running this script (we have the input target_files
1501 # zip only). For such cases, we additionally scan other locations (e.g.
1502 # IMAGES/, RADIO/, etc) before bailing out.
1503 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001504 chained_image = split_args[index + 1]
1505 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001506 continue
1507 found = False
1508 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1509 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001510 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001511 if os.path.exists(alt_path):
1512 split_args[index + 1] = alt_path
1513 found = True
1514 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001515 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001516 cmd.extend(split_args)
1517
1518 RunAndCheckOutput(cmd)
1519
1520
jiajia tang836f76b2021-04-02 14:48:26 +08001521def _MakeRamdisk(sourcedir, fs_config_file=None,
1522 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001523 ramdisk_img = tempfile.NamedTemporaryFile()
1524
1525 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1526 cmd = ["mkbootfs", "-f", fs_config_file,
1527 os.path.join(sourcedir, "RAMDISK")]
1528 else:
1529 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1530 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001531 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001532 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001533 stdout=ramdisk_img.file.fileno())
Luca Stefani7f2913c2020-06-11 13:03:18 +02001534 elif ramdisk_format == RamdiskFormat.XZ:
1535 p2 = Run(["xz", "-f", "-c", "--check=crc32", "--lzma2=dict=32MiB"], stdin=p1.stdout,
1536 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001537 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001538 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001539 else:
Luca Stefani7f2913c2020-06-11 13:03:18 +02001540 raise ValueError("Only support lz4, xz, or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001541
1542 p2.wait()
1543 p1.wait()
1544 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001545 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001546
1547 return ramdisk_img
1548
1549
Steve Muckle9793cf62020-04-08 18:27:00 -07001550def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001551 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001552 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001553
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001554 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001555 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1556 we are building a two-step special image (i.e. building a recovery image to
1557 be loaded into /boot in two-step OTAs).
1558
1559 Return the image data, or None if sourcedir does not appear to contains files
1560 for building the requested image.
1561 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001562
Yifan Hong63c5ca12020-10-08 11:54:02 -07001563 if info_dict is None:
1564 info_dict = OPTIONS.info_dict
1565
Steve Muckle9793cf62020-04-08 18:27:00 -07001566 # "boot" or "recovery", without extension.
1567 partition_name = os.path.basename(sourcedir).lower()
1568
Yifan Hong63c5ca12020-10-08 11:54:02 -07001569 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001570 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001571 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1572 logger.info("Excluded kernel binary from recovery image.")
1573 else:
1574 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001575 elif partition_name == "init_boot":
1576 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001577 else:
1578 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001579 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001580 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001581 return None
1582
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001583 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1584
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001585 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001586 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001587
Doug Zongkereef39442009-04-02 12:14:19 -07001588 img = tempfile.NamedTemporaryFile()
1589
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001590 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001591 ramdisk_format = _GetRamdiskFormat(info_dict)
1592 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1593 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001594
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001595 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1596 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1597
Yifan Hong63c5ca12020-10-08 11:54:02 -07001598 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001599 if kernel_path is not None:
1600 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001601
Benoit Fradina45a8682014-07-14 21:00:43 +02001602 fn = os.path.join(sourcedir, "second")
1603 if os.access(fn, os.F_OK):
1604 cmd.append("--second")
1605 cmd.append(fn)
1606
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001607 fn = os.path.join(sourcedir, "dtb")
1608 if os.access(fn, os.F_OK):
1609 cmd.append("--dtb")
1610 cmd.append(fn)
1611
Doug Zongker171f1cd2009-06-15 22:36:37 -07001612 fn = os.path.join(sourcedir, "cmdline")
1613 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001614 cmd.append("--cmdline")
1615 cmd.append(open(fn).read().rstrip("\n"))
1616
1617 fn = os.path.join(sourcedir, "base")
1618 if os.access(fn, os.F_OK):
1619 cmd.append("--base")
1620 cmd.append(open(fn).read().rstrip("\n"))
1621
Ying Wang4de6b5b2010-08-25 14:29:34 -07001622 fn = os.path.join(sourcedir, "pagesize")
1623 if os.access(fn, os.F_OK):
1624 cmd.append("--pagesize")
1625 cmd.append(open(fn).read().rstrip("\n"))
1626
David Ng296bfaa2012-07-27 18:39:48 -07001627 fn = os.path.join(sourcedir, "dt")
1628 if os.access(fn, os.F_OK):
1629 cmd.append("--dt")
1630 cmd.append(fn)
1631
Steve Mucklef84668e2020-03-16 19:13:46 -07001632 if partition_name == "recovery":
1633 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301634 if not args:
1635 # Fall back to "mkbootimg_args" for recovery image
1636 # in case "recovery_mkbootimg_args" is not set.
1637 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001638 elif partition_name == "init_boot":
1639 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001640 else:
1641 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001642 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001643 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001644
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001645 args = info_dict.get("mkbootimg_version_args")
1646 if args and args.strip():
1647 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001648
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001649 if has_ramdisk:
1650 cmd.extend(["--ramdisk", ramdisk_img.name])
1651
Tao Baod95e9fd2015-03-29 23:07:41 -07001652 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001653 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001654 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001655 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001656 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001657 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001658
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001659 if partition_name == "recovery":
1660 if info_dict.get("include_recovery_dtbo") == "true":
1661 fn = os.path.join(sourcedir, "recovery_dtbo")
1662 cmd.extend(["--recovery_dtbo", fn])
1663 if info_dict.get("include_recovery_acpio") == "true":
1664 fn = os.path.join(sourcedir, "recovery_acpio")
1665 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001666
Tao Bao986ee862018-10-04 15:46:16 -07001667 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001668
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001669 if _HasGkiCertificationArgs():
1670 if not os.path.exists(img.name):
1671 raise ValueError("Cannot find GKI boot.img")
1672 if kernel_path is None or not os.path.exists(kernel_path):
1673 raise ValueError("Cannot find GKI kernel.img")
1674
1675 # Certify GKI images.
1676 boot_signature_bytes = b''
1677 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1678 boot_signature_bytes += _GenerateGkiCertificate(
1679 kernel_path, "generic_kernel")
1680
1681 BOOT_SIGNATURE_SIZE = 16 * 1024
1682 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1683 raise ValueError(
1684 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1685 boot_signature_bytes += (
1686 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1687 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1688
1689 with open(img.name, 'ab') as f:
1690 f.write(boot_signature_bytes)
1691
Tao Bao76def242017-11-21 09:25:31 -08001692 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001693 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001694 # Hard-code the path as "/boot" for two-step special recovery image (which
1695 # will be loaded into /boot during the two-step OTA).
1696 if two_step_image:
1697 path = "/boot"
1698 else:
Tao Baobf70c312017-07-11 17:27:55 -07001699 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001700 cmd = [OPTIONS.boot_signer_path]
1701 cmd.extend(OPTIONS.boot_signer_args)
1702 cmd.extend([path, img.name,
1703 info_dict["verity_key"] + ".pk8",
1704 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001705 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001706
Tao Baod95e9fd2015-03-29 23:07:41 -07001707 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001708 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001709 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001710 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001711 # We have switched from the prebuilt futility binary to using the tool
1712 # (futility-host) built from the source. Override the setting in the old
1713 # TF.zip.
1714 futility = info_dict["futility"]
1715 if futility.startswith("prebuilts/"):
1716 futility = "futility-host"
1717 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001718 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001719 info_dict["vboot_key"] + ".vbprivk",
1720 info_dict["vboot_subkey"] + ".vbprivk",
1721 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001722 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001723 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001724
Tao Baof3282b42015-04-01 11:21:55 -07001725 # Clean up the temp files.
1726 img_unsigned.close()
1727 img_keyblock.close()
1728
David Zeuthen8fecb282017-12-01 16:24:01 -05001729 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001730 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001731 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001732 if partition_name == "recovery":
1733 part_size = info_dict["recovery_size"]
1734 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001735 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001736 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001737 "--partition_size", str(part_size), "--partition_name",
1738 partition_name]
1739 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001740 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001741 if args and args.strip():
1742 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001743 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001744
1745 img.seek(os.SEEK_SET, 0)
1746 data = img.read()
1747
1748 if has_ramdisk:
1749 ramdisk_img.close()
1750 img.close()
1751
1752 return data
1753
1754
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001755def _SignBootableImage(image_path, prebuilt_name, partition_name,
1756 info_dict=None):
1757 """Performs AVB signing for a prebuilt boot.img.
1758
1759 Args:
1760 image_path: The full path of the image, e.g., /path/to/boot.img.
1761 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001762 boot-5.10.img, recovery.img or init_boot.img.
1763 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001764 info_dict: The information dict read from misc_info.txt.
1765 """
1766 if info_dict is None:
1767 info_dict = OPTIONS.info_dict
1768
1769 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1770 if info_dict.get("avb_enable") == "true":
1771 avbtool = info_dict["avb_avbtool"]
1772 if partition_name == "recovery":
1773 part_size = info_dict["recovery_size"]
1774 else:
1775 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1776
1777 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1778 "--partition_size", str(part_size), "--partition_name",
1779 partition_name]
1780 AppendAVBSigningArgs(cmd, partition_name)
1781 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1782 if args and args.strip():
1783 cmd.extend(shlex.split(args))
1784 RunAndCheckOutput(cmd)
1785
1786
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001787def HasRamdisk(partition_name, info_dict=None):
1788 """Returns true/false to see if a bootable image should have a ramdisk.
1789
1790 Args:
1791 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1792 info_dict: The information dict read from misc_info.txt.
1793 """
1794 if info_dict is None:
1795 info_dict = OPTIONS.info_dict
1796
1797 if partition_name != "boot":
1798 return True # init_boot.img or recovery.img has a ramdisk.
1799
1800 if info_dict.get("recovery_as_boot") == "true":
1801 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1802
Bowgo Tsai85578e02022-04-19 10:50:59 +08001803 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1804 return False # A GKI boot.img has no ramdisk since Android-13.
1805
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001806 if info_dict.get("system_root_image") == "true":
1807 # The ramdisk content is merged into the system.img, so there is NO
1808 # ramdisk in the boot.img or boot-<kernel version>.img.
1809 return False
1810
1811 if info_dict.get("init_boot") == "true":
1812 # The ramdisk is moved to the init_boot.img, so there is NO
1813 # ramdisk in the boot.img or boot-<kernel version>.img.
1814 return False
1815
1816 return True
1817
1818
Doug Zongkerd5131602012-08-02 14:46:42 -07001819def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001820 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001821 """Return a File object with the desired bootable image.
1822
1823 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1824 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1825 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001826
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001827 if info_dict is None:
1828 info_dict = OPTIONS.info_dict
1829
Doug Zongker55d93282011-01-25 17:03:34 -08001830 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1831 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001832 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001833 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001834
1835 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1836 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001837 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001838 return File.FromLocalFile(name, prebuilt_path)
1839
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001840 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001841 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1842 if os.path.exists(prebuilt_path):
1843 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1844 signed_img = MakeTempFile()
1845 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001846 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1847 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001848
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001849 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001850
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001851 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001852
Doug Zongker6f1d0312014-08-22 08:07:12 -07001853 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001854 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001855 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001856 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001857 if data:
1858 return File(name, data)
1859 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001860
Doug Zongkereef39442009-04-02 12:14:19 -07001861
Lucas Wei03230252022-04-18 16:00:40 +08001862def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001863 """Build a vendor boot image from the specified sourcedir.
1864
1865 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1866 turn them into a vendor boot image.
1867
1868 Return the image data, or None if sourcedir does not appear to contains files
1869 for building the requested image.
1870 """
1871
1872 if info_dict is None:
1873 info_dict = OPTIONS.info_dict
1874
1875 img = tempfile.NamedTemporaryFile()
1876
jiajia tang836f76b2021-04-02 14:48:26 +08001877 ramdisk_format = _GetRamdiskFormat(info_dict)
1878 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001879
1880 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1881 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1882
1883 cmd = [mkbootimg]
1884
1885 fn = os.path.join(sourcedir, "dtb")
1886 if os.access(fn, os.F_OK):
Lucas Wei03230252022-04-18 16:00:40 +08001887 has_vendor_kernel_boot = (info_dict.get("vendor_kernel_boot", "").lower() == "true")
1888
1889 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1890 # Otherwise pack dtb into vendor_boot.
1891 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1892 cmd.append("--dtb")
1893 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001894
1895 fn = os.path.join(sourcedir, "vendor_cmdline")
1896 if os.access(fn, os.F_OK):
1897 cmd.append("--vendor_cmdline")
1898 cmd.append(open(fn).read().rstrip("\n"))
1899
1900 fn = os.path.join(sourcedir, "base")
1901 if os.access(fn, os.F_OK):
1902 cmd.append("--base")
1903 cmd.append(open(fn).read().rstrip("\n"))
1904
1905 fn = os.path.join(sourcedir, "pagesize")
1906 if os.access(fn, os.F_OK):
1907 cmd.append("--pagesize")
1908 cmd.append(open(fn).read().rstrip("\n"))
1909
1910 args = info_dict.get("mkbootimg_args")
1911 if args and args.strip():
1912 cmd.extend(shlex.split(args))
1913
1914 args = info_dict.get("mkbootimg_version_args")
1915 if args and args.strip():
1916 cmd.extend(shlex.split(args))
1917
1918 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1919 cmd.extend(["--vendor_boot", img.name])
1920
Devin Moore50509012021-01-13 10:45:04 -08001921 fn = os.path.join(sourcedir, "vendor_bootconfig")
1922 if os.access(fn, os.F_OK):
1923 cmd.append("--vendor_bootconfig")
1924 cmd.append(fn)
1925
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001926 ramdisk_fragment_imgs = []
1927 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1928 if os.access(fn, os.F_OK):
1929 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1930 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001931 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1932 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001933 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001934 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1935 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001936 # Use prebuilt image if found, else create ramdisk from supplied files.
1937 if os.access(fn, os.F_OK):
1938 ramdisk_fragment_pathname = fn
1939 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001940 ramdisk_fragment_root = os.path.join(
1941 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001942 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1943 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001944 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1945 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1946 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1947
Steve Mucklee1b10862019-07-10 10:49:37 -07001948 RunAndCheckOutput(cmd)
1949
1950 # AVB: if enabled, calculate and add hash.
1951 if info_dict.get("avb_enable") == "true":
1952 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001953 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001954 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001955 "--partition_size", str(part_size), "--partition_name", partition_name]
1956 AppendAVBSigningArgs(cmd, partition_name)
1957 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001958 if args and args.strip():
1959 cmd.extend(shlex.split(args))
1960 RunAndCheckOutput(cmd)
1961
1962 img.seek(os.SEEK_SET, 0)
1963 data = img.read()
1964
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001965 for f in ramdisk_fragment_imgs:
1966 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001967 ramdisk_img.close()
1968 img.close()
1969
1970 return data
1971
1972
1973def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1974 info_dict=None):
1975 """Return a File object with the desired vendor boot image.
1976
1977 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1978 the source files in 'unpack_dir'/'tree_subdir'."""
1979
1980 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1981 if os.path.exists(prebuilt_path):
1982 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1983 return File.FromLocalFile(name, prebuilt_path)
1984
1985 logger.info("building image from target_files %s...", tree_subdir)
1986
1987 if info_dict is None:
1988 info_dict = OPTIONS.info_dict
1989
Kelvin Zhang0876c412020-06-23 15:06:58 -04001990 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001991 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1992 if data:
1993 return File(name, data)
1994 return None
1995
1996
1997def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1998 info_dict=None):
1999 """Return a File object with the desired vendor kernel boot image.
2000
2001 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
2002 the source files in 'unpack_dir'/'tree_subdir'."""
2003
2004 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
2005 if os.path.exists(prebuilt_path):
2006 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2007 return File.FromLocalFile(name, prebuilt_path)
2008
2009 logger.info("building image from target_files %s...", tree_subdir)
2010
2011 if info_dict is None:
2012 info_dict = OPTIONS.info_dict
2013
2014 data = _BuildVendorBootImage(
2015 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002016 if data:
2017 return File(name, data)
2018 return None
2019
2020
Narayan Kamatha07bf042017-08-14 14:49:21 +01002021def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002022 """Gunzips the given gzip compressed file to a given output file."""
2023 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002024 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002025 shutil.copyfileobj(in_file, out_file)
2026
2027
Tao Bao0ff15de2019-03-20 11:26:06 -07002028def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002029 """Unzips the archive to the given directory.
2030
2031 Args:
2032 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002033 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002034 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2035 archvie. Non-matching patterns will be filtered out. If there's no match
2036 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002037 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002038 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002039 if patterns is not None:
2040 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002041 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002042 names = input_zip.namelist()
2043 filtered = [
2044 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2045
2046 # There isn't any matching files. Don't unzip anything.
2047 if not filtered:
2048 return
2049 cmd.extend(filtered)
2050
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002051 RunAndCheckOutput(cmd)
2052
2053
Daniel Norman78554ea2021-09-14 10:29:38 -07002054def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002055 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002056
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002057 Args:
2058 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2059 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2060
Daniel Norman78554ea2021-09-14 10:29:38 -07002061 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002062 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002063
Tao Bao1c830bf2017-12-25 10:43:47 -08002064 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002065 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002066 """
Doug Zongkereef39442009-04-02 12:14:19 -07002067
Tao Bao1c830bf2017-12-25 10:43:47 -08002068 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002069 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2070 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002071 UnzipToDir(m.group(1), tmp, patterns)
2072 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002073 filename = m.group(1)
2074 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002075 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002076
Tao Baodba59ee2018-01-09 13:21:02 -08002077 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002078
2079
Yifan Hong8a66a712019-04-04 15:37:57 -07002080def GetUserImage(which, tmpdir, input_zip,
2081 info_dict=None,
2082 allow_shared_blocks=None,
2083 hashtree_info_generator=None,
2084 reset_file_map=False):
2085 """Returns an Image object suitable for passing to BlockImageDiff.
2086
2087 This function loads the specified image from the given path. If the specified
2088 image is sparse, it also performs additional processing for OTA purpose. For
2089 example, it always adds block 0 to clobbered blocks list. It also detects
2090 files that cannot be reconstructed from the block list, for whom we should
2091 avoid applying imgdiff.
2092
2093 Args:
2094 which: The partition name.
2095 tmpdir: The directory that contains the prebuilt image and block map file.
2096 input_zip: The target-files ZIP archive.
2097 info_dict: The dict to be looked up for relevant info.
2098 allow_shared_blocks: If image is sparse, whether having shared blocks is
2099 allowed. If none, it is looked up from info_dict.
2100 hashtree_info_generator: If present and image is sparse, generates the
2101 hashtree_info for this sparse image.
2102 reset_file_map: If true and image is sparse, reset file map before returning
2103 the image.
2104 Returns:
2105 A Image object. If it is a sparse image and reset_file_map is False, the
2106 image will have file_map info loaded.
2107 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002108 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002109 info_dict = LoadInfoDict(input_zip)
2110
2111 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002112 if info_dict.get(which + "_disable_sparse"):
2113 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002114
2115 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2116 # shared blocks (i.e. some blocks will show up in multiple files' block
2117 # list). We can only allocate such shared blocks to the first "owner", and
2118 # disable imgdiff for all later occurrences.
2119 if allow_shared_blocks is None:
2120 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2121
2122 if is_sparse:
2123 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2124 hashtree_info_generator)
2125 if reset_file_map:
2126 img.ResetFileMap()
2127 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002128 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002129
2130
2131def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2132 """Returns a Image object suitable for passing to BlockImageDiff.
2133
2134 This function loads the specified non-sparse image from the given path.
2135
2136 Args:
2137 which: The partition name.
2138 tmpdir: The directory that contains the prebuilt image and block map file.
2139 Returns:
2140 A Image object.
2141 """
2142 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2143 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2144
2145 # The image and map files must have been created prior to calling
2146 # ota_from_target_files.py (since LMP).
2147 assert os.path.exists(path) and os.path.exists(mappath)
2148
Tianjie Xu41976c72019-07-03 13:57:01 -07002149 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2150
Yifan Hong8a66a712019-04-04 15:37:57 -07002151
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002152def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2153 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002154 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2155
2156 This function loads the specified sparse image from the given path, and
2157 performs additional processing for OTA purpose. For example, it always adds
2158 block 0 to clobbered blocks list. It also detects files that cannot be
2159 reconstructed from the block list, for whom we should avoid applying imgdiff.
2160
2161 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002162 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002163 tmpdir: The directory that contains the prebuilt image and block map file.
2164 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002165 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002166 hashtree_info_generator: If present, generates the hashtree_info for this
2167 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002168 Returns:
2169 A SparseImage object, with file_map info loaded.
2170 """
Tao Baoc765cca2018-01-31 17:32:40 -08002171 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2172 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2173
2174 # The image and map files must have been created prior to calling
2175 # ota_from_target_files.py (since LMP).
2176 assert os.path.exists(path) and os.path.exists(mappath)
2177
2178 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2179 # it to clobbered_blocks so that it will be written to the target
2180 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2181 clobbered_blocks = "0"
2182
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002183 image = sparse_img.SparseImage(
2184 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2185 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002186
2187 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2188 # if they contain all zeros. We can't reconstruct such a file from its block
2189 # list. Tag such entries accordingly. (Bug: 65213616)
2190 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002191 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002192 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002193 continue
2194
Tom Cherryd14b8952018-08-09 14:26:00 -07002195 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2196 # filename listed in system.map may contain an additional leading slash
2197 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2198 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002199 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002200 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002201 arcname = entry.lstrip('/')
2202 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002203 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002204 else:
2205 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002206
2207 assert arcname in input_zip.namelist(), \
2208 "Failed to find the ZIP entry for {}".format(entry)
2209
Tao Baoc765cca2018-01-31 17:32:40 -08002210 info = input_zip.getinfo(arcname)
2211 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002212
2213 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002214 # image, check the original block list to determine its completeness. Note
2215 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002216 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002217 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002218
Tao Baoc765cca2018-01-31 17:32:40 -08002219 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2220 ranges.extra['incomplete'] = True
2221
2222 return image
2223
2224
Doug Zongkereef39442009-04-02 12:14:19 -07002225def GetKeyPasswords(keylist):
2226 """Given a list of keys, prompt the user to enter passwords for
2227 those which require them. Return a {key: password} dict. password
2228 will be None if the key has no password."""
2229
Doug Zongker8ce7c252009-05-22 13:34:54 -07002230 no_passwords = []
2231 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002232 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002233 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002234
2235 # sorted() can't compare strings to None, so convert Nones to strings
2236 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002237 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002238 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002239 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002240 continue
2241
T.R. Fullhart37e10522013-03-18 10:31:26 -07002242 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002243 "-inform", "DER", "-nocrypt"],
2244 stdin=devnull.fileno(),
2245 stdout=devnull.fileno(),
2246 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002247 p.communicate()
2248 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002249 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002250 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002251 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002252 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2253 "-inform", "DER", "-passin", "pass:"],
2254 stdin=devnull.fileno(),
2255 stdout=devnull.fileno(),
2256 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002257 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002258 if p.returncode == 0:
2259 # Encrypted key with empty string as password.
2260 key_passwords[k] = ''
2261 elif stderr.startswith('Error decrypting key'):
2262 # Definitely encrypted key.
2263 # It would have said "Error reading key" if it didn't parse correctly.
2264 need_passwords.append(k)
2265 else:
2266 # Potentially, a type of key that openssl doesn't understand.
2267 # We'll let the routines in signapk.jar handle it.
2268 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002269 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002270
T.R. Fullhart37e10522013-03-18 10:31:26 -07002271 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002272 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002273 return key_passwords
2274
2275
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002276def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002277 """Gets the minSdkVersion declared in the APK.
2278
Martin Stjernholm58472e82022-01-07 22:08:47 +00002279 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2280 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002281
2282 Args:
2283 apk_name: The APK filename.
2284
2285 Returns:
2286 The parsed SDK version string.
2287
2288 Raises:
2289 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002290 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002291 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002292 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002293 stderr=subprocess.PIPE)
2294 stdoutdata, stderrdata = proc.communicate()
2295 if proc.returncode != 0:
2296 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002297 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2298 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002299
Tao Baof47bf0f2018-03-21 23:28:51 -07002300 for line in stdoutdata.split("\n"):
2301 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002302 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2303 if m:
2304 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002305 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002306
2307
2308def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002309 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002310
Tao Baof47bf0f2018-03-21 23:28:51 -07002311 If minSdkVersion is set to a codename, it is translated to a number using the
2312 provided map.
2313
2314 Args:
2315 apk_name: The APK filename.
2316
2317 Returns:
2318 The parsed SDK version number.
2319
2320 Raises:
2321 ExternalError: On failing to get the min SDK version number.
2322 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002323 version = GetMinSdkVersion(apk_name)
2324 try:
2325 return int(version)
2326 except ValueError:
Paul Duffina03f1262023-02-01 12:12:51 +00002327 # Not a decimal number.
2328 #
2329 # It could be either a straight codename, e.g.
2330 # UpsideDownCake
2331 #
2332 # Or a codename with API fingerprint SHA, e.g.
2333 # UpsideDownCake.e7d3947f14eb9dc4fec25ff6c5f8563e
2334 #
2335 # Extract the codename and try and map it to a version number.
2336 split = version.split(".")
2337 codename = split[0]
2338 if codename in codename_to_api_level_map:
2339 return codename_to_api_level_map[codename]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002340 raise ExternalError(
Paul Duffina03f1262023-02-01 12:12:51 +00002341 "Unknown codename: '{}' from minSdkVersion: '{}'. Known codenames: {}".format(
2342 codename, version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002343
2344
2345def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002346 codename_to_api_level_map=None, whole_file=False,
2347 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002348 """Sign the input_name zip/jar/apk, producing output_name. Use the
2349 given key and password (the latter may be None if the key does not
2350 have a password.
2351
Doug Zongker951495f2009-08-14 12:44:19 -07002352 If whole_file is true, use the "-w" option to SignApk to embed a
2353 signature that covers the whole file in the archive comment of the
2354 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002355
2356 min_api_level is the API Level (int) of the oldest platform this file may end
2357 up on. If not specified for an APK, the API Level is obtained by interpreting
2358 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2359
2360 codename_to_api_level_map is needed to translate the codename which may be
2361 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002362
2363 Caller may optionally specify extra args to be passed to SignApk, which
2364 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002365 """
Tao Bao76def242017-11-21 09:25:31 -08002366 if codename_to_api_level_map is None:
2367 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002368 if extra_signapk_args is None:
2369 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002370
Alex Klyubin9667b182015-12-10 13:38:50 -08002371 java_library_path = os.path.join(
2372 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2373
Tao Baoe95540e2016-11-08 12:08:53 -08002374 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2375 ["-Djava.library.path=" + java_library_path,
2376 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002377 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002378 if whole_file:
2379 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002380
2381 min_sdk_version = min_api_level
2382 if min_sdk_version is None:
2383 if not whole_file:
2384 min_sdk_version = GetMinSdkVersionInt(
2385 input_name, codename_to_api_level_map)
2386 if min_sdk_version is not None:
2387 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2388
T.R. Fullhart37e10522013-03-18 10:31:26 -07002389 cmd.extend([key + OPTIONS.public_key_suffix,
2390 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002391 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002392
Tao Bao73dd4f42018-10-04 16:25:33 -07002393 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002394 if password is not None:
2395 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002396 stdoutdata, _ = proc.communicate(password)
2397 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002398 raise ExternalError(
2399 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002400 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002401
Doug Zongkereef39442009-04-02 12:14:19 -07002402
Doug Zongker37974732010-09-16 17:44:38 -07002403def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002404 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002405
Tao Bao9dd909e2017-11-14 11:27:32 -08002406 For non-AVB images, raise exception if the data is too big. Print a warning
2407 if the data is nearing the maximum size.
2408
2409 For AVB images, the actual image size should be identical to the limit.
2410
2411 Args:
2412 data: A string that contains all the data for the partition.
2413 target: The partition name. The ".img" suffix is optional.
2414 info_dict: The dict to be looked up for relevant info.
2415 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002416 if target.endswith(".img"):
2417 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002418 mount_point = "/" + target
2419
Ying Wangf8824af2014-06-03 14:07:27 -07002420 fs_type = None
2421 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002422 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002423 if mount_point == "/userdata":
2424 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002425 p = info_dict["fstab"][mount_point]
2426 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002427 device = p.device
2428 if "/" in device:
2429 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002430 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002431 if not fs_type or not limit:
2432 return
Doug Zongkereef39442009-04-02 12:14:19 -07002433
Andrew Boie0f9aec82012-02-14 09:32:52 -08002434 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002435 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2436 # path.
2437 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2438 if size != limit:
2439 raise ExternalError(
2440 "Mismatching image size for %s: expected %d actual %d" % (
2441 target, limit, size))
2442 else:
2443 pct = float(size) * 100.0 / limit
2444 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2445 if pct >= 99.0:
2446 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002447
2448 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002449 logger.warning("\n WARNING: %s\n", msg)
2450 else:
2451 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002452
2453
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002454def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002455 """Parses the APK certs info from a given target-files zip.
2456
2457 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2458 tuple with the following elements: (1) a dictionary that maps packages to
2459 certs (based on the "certificate" and "private_key" attributes in the file;
2460 (2) a string representing the extension of compressed APKs in the target files
2461 (e.g ".gz", ".bro").
2462
2463 Args:
2464 tf_zip: The input target_files ZipFile (already open).
2465
2466 Returns:
2467 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2468 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2469 no compressed APKs.
2470 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002471 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002472 compressed_extension = None
2473
Tao Bao0f990332017-09-08 19:02:54 -07002474 # META/apkcerts.txt contains the info for _all_ the packages known at build
2475 # time. Filter out the ones that are not installed.
2476 installed_files = set()
2477 for name in tf_zip.namelist():
2478 basename = os.path.basename(name)
2479 if basename:
2480 installed_files.add(basename)
2481
Tao Baoda30cfa2017-12-01 16:19:46 -08002482 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002483 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002484 if not line:
2485 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002486 m = re.match(
2487 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002488 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2489 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002490 line)
2491 if not m:
2492 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002493
Tao Bao818ddf52018-01-05 11:17:34 -08002494 matches = m.groupdict()
2495 cert = matches["CERT"]
2496 privkey = matches["PRIVKEY"]
2497 name = matches["NAME"]
2498 this_compressed_extension = matches["COMPRESSED"]
2499
2500 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2501 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2502 if cert in SPECIAL_CERT_STRINGS and not privkey:
2503 certmap[name] = cert
2504 elif (cert.endswith(OPTIONS.public_key_suffix) and
2505 privkey.endswith(OPTIONS.private_key_suffix) and
2506 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2507 certmap[name] = cert[:-public_key_suffix_len]
2508 else:
2509 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2510
2511 if not this_compressed_extension:
2512 continue
2513
2514 # Only count the installed files.
2515 filename = name + '.' + this_compressed_extension
2516 if filename not in installed_files:
2517 continue
2518
2519 # Make sure that all the values in the compression map have the same
2520 # extension. We don't support multiple compression methods in the same
2521 # system image.
2522 if compressed_extension:
2523 if this_compressed_extension != compressed_extension:
2524 raise ValueError(
2525 "Multiple compressed extensions: {} vs {}".format(
2526 compressed_extension, this_compressed_extension))
2527 else:
2528 compressed_extension = this_compressed_extension
2529
2530 return (certmap,
2531 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002532
2533
Doug Zongkereef39442009-04-02 12:14:19 -07002534COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002535Global options
2536
2537 -p (--path) <dir>
2538 Prepend <dir>/bin to the list of places to search for binaries run by this
2539 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002540
Doug Zongker05d3dea2009-06-22 11:32:31 -07002541 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002542 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002543
Tao Bao30df8b42018-04-23 15:32:53 -07002544 -x (--extra) <key=value>
2545 Add a key/value pair to the 'extras' dict, which device-specific extension
2546 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002547
Doug Zongkereef39442009-04-02 12:14:19 -07002548 -v (--verbose)
2549 Show command lines being executed.
2550
2551 -h (--help)
2552 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002553
2554 --logfile <file>
2555 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002556"""
2557
Kelvin Zhang0876c412020-06-23 15:06:58 -04002558
Doug Zongkereef39442009-04-02 12:14:19 -07002559def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002560 print(docstring.rstrip("\n"))
2561 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002562
2563
2564def ParseOptions(argv,
2565 docstring,
2566 extra_opts="", extra_long_opts=(),
2567 extra_option_handler=None):
2568 """Parse the options in argv and return any arguments that aren't
2569 flags. docstring is the calling module's docstring, to be displayed
2570 for errors and -h. extra_opts and extra_long_opts are for flags
2571 defined by the caller, which are processed by passing them to
2572 extra_option_handler."""
2573
2574 try:
2575 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002576 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002577 ["help", "verbose", "path=", "signapk_path=",
Martin Stjernholm58472e82022-01-07 22:08:47 +00002578 "signapk_shared_library_path=", "extra_signapk_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002579 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002580 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2581 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002582 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002583 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002584 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002585 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002586 sys.exit(2)
2587
Doug Zongkereef39442009-04-02 12:14:19 -07002588 for o, a in opts:
2589 if o in ("-h", "--help"):
2590 Usage(docstring)
2591 sys.exit()
2592 elif o in ("-v", "--verbose"):
2593 OPTIONS.verbose = True
2594 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002595 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002596 elif o in ("--signapk_path",):
2597 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002598 elif o in ("--signapk_shared_library_path",):
2599 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002600 elif o in ("--extra_signapk_args",):
2601 OPTIONS.extra_signapk_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002602 elif o in ("--aapt2_path",):
2603 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002604 elif o in ("--java_path",):
2605 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002606 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002607 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002608 elif o in ("--android_jar_path",):
2609 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002610 elif o in ("--public_key_suffix",):
2611 OPTIONS.public_key_suffix = a
2612 elif o in ("--private_key_suffix",):
2613 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002614 elif o in ("--boot_signer_path",):
2615 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002616 elif o in ("--boot_signer_args",):
2617 OPTIONS.boot_signer_args = shlex.split(a)
2618 elif o in ("--verity_signer_path",):
2619 OPTIONS.verity_signer_path = a
2620 elif o in ("--verity_signer_args",):
2621 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002622 elif o in ("-s", "--device_specific"):
2623 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002624 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002625 key, value = a.split("=", 1)
2626 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002627 elif o in ("--logfile",):
2628 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002629 else:
2630 if extra_option_handler is None or not extra_option_handler(o, a):
2631 assert False, "unknown option \"%s\"" % (o,)
2632
Doug Zongker85448772014-09-09 14:59:20 -07002633 if OPTIONS.search_path:
2634 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2635 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002636
2637 return args
2638
2639
Tao Bao4c851b12016-09-19 13:54:38 -07002640def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002641 """Make a temp file and add it to the list of things to be deleted
2642 when Cleanup() is called. Return the filename."""
2643 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2644 os.close(fd)
2645 OPTIONS.tempfiles.append(fn)
2646 return fn
2647
2648
Tao Bao1c830bf2017-12-25 10:43:47 -08002649def MakeTempDir(prefix='tmp', suffix=''):
2650 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2651
2652 Returns:
2653 The absolute pathname of the new directory.
2654 """
2655 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2656 OPTIONS.tempfiles.append(dir_name)
2657 return dir_name
2658
2659
Doug Zongkereef39442009-04-02 12:14:19 -07002660def Cleanup():
2661 for i in OPTIONS.tempfiles:
2662 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002663 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002664 else:
2665 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002666 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002667
2668
2669class PasswordManager(object):
2670 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002671 self.editor = os.getenv("EDITOR")
2672 self.pwfile = os.getenv("ANDROID_PW_FILE")
Tom Powell68ba8192017-01-20 20:47:49 -08002673 self.secure_storage_cmd = os.getenv("ANDROID_SECURE_STORAGE_CMD", None)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002674
2675 def GetPasswords(self, items):
2676 """Get passwords corresponding to each string in 'items',
2677 returning a dict. (The dict may have keys in addition to the
2678 values in 'items'.)
2679
2680 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2681 user edit that file to add more needed passwords. If no editor is
2682 available, or $ANDROID_PW_FILE isn't define, prompts the user
2683 interactively in the ordinary way.
2684 """
2685
2686 current = self.ReadFile()
2687
2688 first = True
2689 while True:
2690 missing = []
2691 for i in items:
2692 if i not in current or not current[i]:
Tom Powell68ba8192017-01-20 20:47:49 -08002693 # Attempt to load using ANDROID_SECURE_STORAGE_CMD
2694 if self.secure_storage_cmd:
2695 try:
2696 os.environ["TMP__KEY_FILE_NAME"] = str(i)
2697 ps = subprocess.Popen(self.secure_storage_cmd, shell=True, stdout=subprocess.PIPE)
2698 output = ps.communicate()[0]
2699 if ps.returncode == 0:
LuK133714813f12022-10-30 20:22:50 +01002700 current[i] = output.decode('utf-8')
LuK13375c8315e2022-12-04 02:07:46 +01002701 else:
2702 logger.warning('Failed to get password for key "%s".', i)
Tom Powell68ba8192017-01-20 20:47:49 -08002703 except Exception as e:
2704 print(e)
2705 pass
2706 if i not in current or not current[i]:
2707 missing.append(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002708 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002709 if not missing:
2710 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002711
2712 for i in missing:
2713 current[i] = ""
2714
2715 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002716 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002717 if sys.version_info[0] >= 3:
2718 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002719 answer = raw_input("try to edit again? [y]> ").strip()
2720 if answer and answer[0] not in 'yY':
2721 raise RuntimeError("key passwords unavailable")
2722 first = False
2723
2724 current = self.UpdateAndReadFile(current)
2725
Kelvin Zhang0876c412020-06-23 15:06:58 -04002726 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002727 """Prompt the user to enter a value (password) for each key in
2728 'current' whose value is fales. Returns a new dict with all the
2729 values.
2730 """
2731 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002732 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002733 if v:
2734 result[k] = v
2735 else:
2736 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002737 result[k] = getpass.getpass(
2738 "Enter password for %s key> " % k).strip()
2739 if result[k]:
2740 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002741 return result
2742
2743 def UpdateAndReadFile(self, current):
2744 if not self.editor or not self.pwfile:
2745 return self.PromptResult(current)
2746
2747 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002748 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002749 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2750 f.write("# (Additional spaces are harmless.)\n\n")
2751
2752 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002753 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002754 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002755 f.write("[[[ %s ]]] %s\n" % (v, k))
2756 if not v and first_line is None:
2757 # position cursor on first line with no password.
2758 first_line = i + 4
2759 f.close()
2760
Tao Bao986ee862018-10-04 15:46:16 -07002761 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002762
2763 return self.ReadFile()
2764
2765 def ReadFile(self):
2766 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002767 if self.pwfile is None:
2768 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002769 try:
2770 f = open(self.pwfile, "r")
2771 for line in f:
2772 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002773 if not line or line[0] == '#':
2774 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002775 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2776 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002777 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002778 else:
2779 result[m.group(2)] = m.group(1)
2780 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002781 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002782 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002783 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002784 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002785
2786
Dan Albert8e0178d2015-01-27 15:53:15 -08002787def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2788 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002789
2790 # http://b/18015246
2791 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2792 # for files larger than 2GiB. We can work around this by adjusting their
2793 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2794 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2795 # it isn't clear to me exactly what circumstances cause this).
2796 # `zipfile.write()` must be used directly to work around this.
2797 #
2798 # This mess can be avoided if we port to python3.
2799 saved_zip64_limit = zipfile.ZIP64_LIMIT
2800 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2801
2802 if compress_type is None:
2803 compress_type = zip_file.compression
2804 if arcname is None:
2805 arcname = filename
2806
2807 saved_stat = os.stat(filename)
2808
2809 try:
2810 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2811 # file to be zipped and reset it when we're done.
2812 os.chmod(filename, perms)
2813
2814 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002815 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2816 # intentional. zip stores datetimes in local time without a time zone
2817 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2818 # in the zip archive.
2819 local_epoch = datetime.datetime.fromtimestamp(0)
2820 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002821 os.utime(filename, (timestamp, timestamp))
2822
2823 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2824 finally:
2825 os.chmod(filename, saved_stat.st_mode)
2826 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2827 zipfile.ZIP64_LIMIT = saved_zip64_limit
2828
2829
Tao Bao58c1b962015-05-20 09:32:18 -07002830def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002831 compress_type=None):
2832 """Wrap zipfile.writestr() function to work around the zip64 limit.
2833
2834 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2835 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2836 when calling crc32(bytes).
2837
2838 But it still works fine to write a shorter string into a large zip file.
2839 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2840 when we know the string won't be too long.
2841 """
2842
2843 saved_zip64_limit = zipfile.ZIP64_LIMIT
2844 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2845
2846 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2847 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002848 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002849 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002850 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002851 else:
Tao Baof3282b42015-04-01 11:21:55 -07002852 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002853 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2854 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2855 # such a case (since
2856 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2857 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2858 # permission bits. We follow the logic in Python 3 to get consistent
2859 # behavior between using the two versions.
2860 if not zinfo.external_attr:
2861 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002862
2863 # If compress_type is given, it overrides the value in zinfo.
2864 if compress_type is not None:
2865 zinfo.compress_type = compress_type
2866
Tao Bao58c1b962015-05-20 09:32:18 -07002867 # If perms is given, it has a priority.
2868 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002869 # If perms doesn't set the file type, mark it as a regular file.
2870 if perms & 0o770000 == 0:
2871 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002872 zinfo.external_attr = perms << 16
2873
Tao Baof3282b42015-04-01 11:21:55 -07002874 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002875 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2876
Dan Albert8b72aef2015-03-23 19:13:21 -07002877 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002878 zipfile.ZIP64_LIMIT = saved_zip64_limit
2879
2880
Tao Bao89d7ab22017-12-14 17:05:33 -08002881def ZipDelete(zip_filename, entries):
2882 """Deletes entries from a ZIP file.
2883
2884 Since deleting entries from a ZIP file is not supported, it shells out to
2885 'zip -d'.
2886
2887 Args:
2888 zip_filename: The name of the ZIP file.
2889 entries: The name of the entry, or the list of names to be deleted.
2890
2891 Raises:
2892 AssertionError: In case of non-zero return from 'zip'.
2893 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002894 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002895 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002896 # If list is empty, nothing to do
2897 if not entries:
2898 return
Tao Bao89d7ab22017-12-14 17:05:33 -08002899 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002900 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002901
2902
Tao Baof3282b42015-04-01 11:21:55 -07002903def ZipClose(zip_file):
2904 # http://b/18015246
2905 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2906 # central directory.
2907 saved_zip64_limit = zipfile.ZIP64_LIMIT
2908 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2909
2910 zip_file.close()
2911
2912 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002913
2914
2915class DeviceSpecificParams(object):
2916 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002917
Doug Zongker05d3dea2009-06-22 11:32:31 -07002918 def __init__(self, **kwargs):
2919 """Keyword arguments to the constructor become attributes of this
2920 object, which is passed to all functions in the device-specific
2921 module."""
Tao Bao38884282019-07-10 22:20:56 -07002922 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002923 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002924 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002925
2926 if self.module is None:
2927 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002928 if not path:
2929 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002930 try:
2931 if os.path.isdir(path):
2932 info = imp.find_module("releasetools", [path])
2933 else:
2934 d, f = os.path.split(path)
2935 b, x = os.path.splitext(f)
2936 if x == ".py":
2937 f = b
2938 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002939 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002940 self.module = imp.load_module("device_specific", *info)
2941 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002942 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002943
2944 def _DoCall(self, function_name, *args, **kwargs):
2945 """Call the named function in the device-specific module, passing
2946 the given args and kwargs. The first argument to the call will be
2947 the DeviceSpecific object itself. If there is no module, or the
2948 module does not define the function, return the value of the
2949 'default' kwarg (which itself defaults to None)."""
2950 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002951 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002952 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2953
2954 def FullOTA_Assertions(self):
2955 """Called after emitting the block of assertions at the top of a
2956 full OTA package. Implementations can add whatever additional
2957 assertions they like."""
2958 return self._DoCall("FullOTA_Assertions")
2959
Doug Zongkere5ff5902012-01-17 10:55:37 -08002960 def FullOTA_InstallBegin(self):
2961 """Called at the start of full OTA installation."""
2962 return self._DoCall("FullOTA_InstallBegin")
2963
Yifan Hong10c530d2018-12-27 17:34:18 -08002964 def FullOTA_GetBlockDifferences(self):
2965 """Called during full 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("FullOTA_GetBlockDifferences")
2970
Doug Zongker05d3dea2009-06-22 11:32:31 -07002971 def FullOTA_InstallEnd(self):
2972 """Called at the end of full OTA installation; typically this is
2973 used to install the image for the device's baseband processor."""
2974 return self._DoCall("FullOTA_InstallEnd")
2975
M1cha82b08a62014-11-25 15:30:48 +01002976 def FullOTA_PostValidate(self):
2977 """Called after installing and validating /system; typically this is
2978 used to resize the system partition after a block based installation."""
2979 return self._DoCall("FullOTA_PostValidate")
2980
Doug Zongker05d3dea2009-06-22 11:32:31 -07002981 def IncrementalOTA_Assertions(self):
2982 """Called after emitting the block of assertions at the top of an
2983 incremental OTA package. Implementations can add whatever
2984 additional assertions they like."""
2985 return self._DoCall("IncrementalOTA_Assertions")
2986
Doug Zongkere5ff5902012-01-17 10:55:37 -08002987 def IncrementalOTA_VerifyBegin(self):
2988 """Called at the start of the verification phase of incremental
2989 OTA installation; additional checks can be placed here to abort
2990 the script before any changes are made."""
2991 return self._DoCall("IncrementalOTA_VerifyBegin")
2992
Doug Zongker05d3dea2009-06-22 11:32:31 -07002993 def IncrementalOTA_VerifyEnd(self):
2994 """Called at the end of the verification phase of incremental OTA
2995 installation; additional checks can be placed here to abort the
2996 script before any changes are made."""
2997 return self._DoCall("IncrementalOTA_VerifyEnd")
2998
Doug Zongkere5ff5902012-01-17 10:55:37 -08002999 def IncrementalOTA_InstallBegin(self):
3000 """Called at the start of incremental OTA installation (after
3001 verification is complete)."""
3002 return self._DoCall("IncrementalOTA_InstallBegin")
3003
Yifan Hong10c530d2018-12-27 17:34:18 -08003004 def IncrementalOTA_GetBlockDifferences(self):
3005 """Called during incremental OTA installation and verification.
3006 Implementation should return a list of BlockDifference objects describing
3007 the update on each additional partitions.
3008 """
3009 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3010
Doug Zongker05d3dea2009-06-22 11:32:31 -07003011 def IncrementalOTA_InstallEnd(self):
3012 """Called at the end of incremental OTA installation; typically
3013 this is used to install the image for the device's baseband
3014 processor."""
3015 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003016
Tao Bao9bc6bb22015-11-09 16:58:28 -08003017 def VerifyOTA_Assertions(self):
3018 return self._DoCall("VerifyOTA_Assertions")
3019
Tao Bao76def242017-11-21 09:25:31 -08003020
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003021class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003022 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003023 self.name = name
3024 self.data = data
3025 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003026 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003027 self.sha1 = sha1(data).hexdigest()
3028
3029 @classmethod
3030 def FromLocalFile(cls, name, diskname):
3031 f = open(diskname, "rb")
3032 data = f.read()
3033 f.close()
3034 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003035
3036 def WriteToTemp(self):
3037 t = tempfile.NamedTemporaryFile()
3038 t.write(self.data)
3039 t.flush()
3040 return t
3041
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003042 def WriteToDir(self, d):
3043 with open(os.path.join(d, self.name), "wb") as fp:
3044 fp.write(self.data)
3045
Geremy Condra36bd3652014-02-06 19:45:10 -08003046 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003047 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003048
Tao Bao76def242017-11-21 09:25:31 -08003049
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003050DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003051 ".gz": "imgdiff",
3052 ".zip": ["imgdiff", "-z"],
3053 ".jar": ["imgdiff", "-z"],
3054 ".apk": ["imgdiff", "-z"],
3055 ".img": "imgdiff",
3056}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003057
Tao Bao76def242017-11-21 09:25:31 -08003058
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003059class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003060 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003061 self.tf = tf
3062 self.sf = sf
3063 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003064 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003065
3066 def ComputePatch(self):
3067 """Compute the patch (as a string of data) needed to turn sf into
3068 tf. Returns the same tuple as GetPatch()."""
3069
3070 tf = self.tf
3071 sf = self.sf
3072
Doug Zongker24cd2802012-08-14 16:36:15 -07003073 if self.diff_program:
3074 diff_program = self.diff_program
3075 else:
3076 ext = os.path.splitext(tf.name)[1]
3077 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003078
3079 ttemp = tf.WriteToTemp()
3080 stemp = sf.WriteToTemp()
3081
3082 ext = os.path.splitext(tf.name)[1]
3083
3084 try:
3085 ptemp = tempfile.NamedTemporaryFile()
3086 if isinstance(diff_program, list):
3087 cmd = copy.copy(diff_program)
3088 else:
3089 cmd = [diff_program]
3090 cmd.append(stemp.name)
3091 cmd.append(ttemp.name)
3092 cmd.append(ptemp.name)
3093 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003094 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003095
Doug Zongkerf8340082014-08-05 10:39:37 -07003096 def run():
3097 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003098 if e:
3099 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003100 th = threading.Thread(target=run)
3101 th.start()
3102 th.join(timeout=300) # 5 mins
3103 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003104 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003105 p.terminate()
3106 th.join(5)
3107 if th.is_alive():
3108 p.kill()
3109 th.join()
3110
Tianjie Xua2a9f992018-01-05 15:15:54 -08003111 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003112 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003113 self.patch = None
3114 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003115 diff = ptemp.read()
3116 finally:
3117 ptemp.close()
3118 stemp.close()
3119 ttemp.close()
3120
3121 self.patch = diff
3122 return self.tf, self.sf, self.patch
3123
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003124 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003125 """Returns a tuple of (target_file, source_file, patch_data).
3126
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003127 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003128 computing the patch failed.
3129 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003130 return self.tf, self.sf, self.patch
3131
3132
3133def ComputeDifferences(diffs):
3134 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003135 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003136
3137 # Do the largest files first, to try and reduce the long-pole effect.
3138 by_size = [(i.tf.size, i) for i in diffs]
3139 by_size.sort(reverse=True)
3140 by_size = [i[1] for i in by_size]
3141
3142 lock = threading.Lock()
3143 diff_iter = iter(by_size) # accessed under lock
3144
3145 def worker():
3146 try:
3147 lock.acquire()
3148 for d in diff_iter:
3149 lock.release()
3150 start = time.time()
3151 d.ComputePatch()
3152 dur = time.time() - start
3153 lock.acquire()
3154
3155 tf, sf, patch = d.GetPatch()
3156 if sf.name == tf.name:
3157 name = tf.name
3158 else:
3159 name = "%s (%s)" % (tf.name, sf.name)
3160 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003161 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003162 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003163 logger.info(
3164 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3165 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003166 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003167 except Exception:
3168 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003169 raise
3170
3171 # start worker threads; wait for them all to finish.
3172 threads = [threading.Thread(target=worker)
3173 for i in range(OPTIONS.worker_threads)]
3174 for th in threads:
3175 th.start()
3176 while threads:
3177 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003178
3179
Dan Albert8b72aef2015-03-23 19:13:21 -07003180class BlockDifference(object):
3181 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003182 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003183 self.tgt = tgt
3184 self.src = src
3185 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003186 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003187 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003188
Tao Baodd2a5892015-03-12 12:32:37 -07003189 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003190 version = max(
3191 int(i) for i in
3192 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003193 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003194 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003195
Tianjie Xu41976c72019-07-03 13:57:01 -07003196 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3197 version=self.version,
3198 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003199 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003200 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003201 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003202 self.touched_src_ranges = b.touched_src_ranges
3203 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003204
Yifan Hong10c530d2018-12-27 17:34:18 -08003205 # On devices with dynamic partitions, for new partitions,
3206 # src is None but OPTIONS.source_info_dict is not.
3207 if OPTIONS.source_info_dict is None:
3208 is_dynamic_build = OPTIONS.info_dict.get(
3209 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003210 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003211 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003212 is_dynamic_build = OPTIONS.source_info_dict.get(
3213 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003214 is_dynamic_source = partition in shlex.split(
3215 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003216
Yifan Hongbb2658d2019-01-25 12:30:58 -08003217 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003218 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3219
Yifan Hongbb2658d2019-01-25 12:30:58 -08003220 # For dynamic partitions builds, check partition list in both source
3221 # and target build because new partitions may be added, and existing
3222 # partitions may be removed.
3223 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3224
Yifan Hong10c530d2018-12-27 17:34:18 -08003225 if is_dynamic:
3226 self.device = 'map_partition("%s")' % partition
3227 else:
3228 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003229 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3230 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003231 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003232 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3233 OPTIONS.source_info_dict)
3234 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003235
Tao Baod8d14be2016-02-04 14:26:02 -08003236 @property
3237 def required_cache(self):
3238 return self._required_cache
3239
Tao Bao76def242017-11-21 09:25:31 -08003240 def WriteScript(self, script, output_zip, progress=None,
3241 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003242 if not self.src:
3243 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003244 script.Print("Patching %s image unconditionally..." % (self.partition,))
3245 else:
3246 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003247
Dan Albert8b72aef2015-03-23 19:13:21 -07003248 if progress:
3249 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003250 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003251
3252 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003253 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003254
Tao Bao9bc6bb22015-11-09 16:58:28 -08003255 def WriteStrictVerifyScript(self, script):
3256 """Verify all the blocks in the care_map, including clobbered blocks.
3257
3258 This differs from the WriteVerifyScript() function: a) it prints different
3259 error messages; b) it doesn't allow half-way updated images to pass the
3260 verification."""
3261
3262 partition = self.partition
3263 script.Print("Verifying %s..." % (partition,))
3264 ranges = self.tgt.care_map
3265 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003266 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003267 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3268 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003269 self.device, ranges_str,
3270 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003271 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003272 script.AppendExtra("")
3273
Tao Baod522bdc2016-04-12 15:53:16 -07003274 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003275 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003276
3277 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003278 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003279 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003280
3281 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003282 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003283 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003284 ranges = self.touched_src_ranges
3285 expected_sha1 = self.touched_src_sha1
3286 else:
3287 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3288 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003289
3290 # No blocks to be checked, skipping.
3291 if not ranges:
3292 return
3293
Tao Bao5ece99d2015-05-12 11:42:31 -07003294 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003295 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003296 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003297 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3298 '"%s.patch.dat")) then' % (
3299 self.device, ranges_str, expected_sha1,
3300 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003301 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003302 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003303
Tianjie Xufc3422a2015-12-15 11:53:59 -08003304 if self.version >= 4:
3305
3306 # Bug: 21124327
3307 # When generating incrementals for the system and vendor partitions in
3308 # version 4 or newer, explicitly check the first block (which contains
3309 # the superblock) of the partition to see if it's what we expect. If
3310 # this check fails, give an explicit log message about the partition
3311 # having been remounted R/W (the most likely explanation).
3312 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003313 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003314
3315 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003316 if partition == "system":
3317 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3318 else:
3319 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003320 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003321 'ifelse (block_image_recover({device}, "{ranges}") && '
3322 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003323 'package_extract_file("{partition}.transfer.list"), '
3324 '"{partition}.new.dat", "{partition}.patch.dat"), '
3325 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003326 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003327 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003328 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003329
Tao Baodd2a5892015-03-12 12:32:37 -07003330 # Abort the OTA update. Note that the incremental OTA cannot be applied
3331 # even if it may match the checksum of the target partition.
3332 # a) If version < 3, operations like move and erase will make changes
3333 # unconditionally and damage the partition.
3334 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003335 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003336 if partition == "system":
3337 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3338 else:
3339 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3340 script.AppendExtra((
3341 'abort("E%d: %s partition has unexpected contents");\n'
3342 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003343
Yifan Hong10c530d2018-12-27 17:34:18 -08003344 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003345 partition = self.partition
3346 script.Print('Verifying the updated %s image...' % (partition,))
3347 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3348 ranges = self.tgt.care_map
3349 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003350 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003351 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003352 self.device, ranges_str,
3353 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003354
3355 # Bug: 20881595
3356 # Verify that extended blocks are really zeroed out.
3357 if self.tgt.extended:
3358 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003359 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003360 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003361 self.device, ranges_str,
3362 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003363 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003364 if partition == "system":
3365 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3366 else:
3367 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003368 script.AppendExtra(
3369 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003370 ' abort("E%d: %s partition has unexpected non-zero contents after '
3371 'OTA update");\n'
3372 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003373 else:
3374 script.Print('Verified the updated %s image.' % (partition,))
3375
Tianjie Xu209db462016-05-24 17:34:52 -07003376 if partition == "system":
3377 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3378 else:
3379 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3380
Tao Bao5fcaaef2015-06-01 13:40:49 -07003381 script.AppendExtra(
3382 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003383 ' abort("E%d: %s partition has unexpected contents after OTA '
3384 'update");\n'
3385 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003386
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003387 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003388 ZipWrite(output_zip,
3389 '{}.transfer.list'.format(self.path),
3390 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003391
Tao Bao76def242017-11-21 09:25:31 -08003392 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3393 # its size. Quailty 9 almost triples the compression time but doesn't
3394 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003395 # zip | brotli(quality 6) | brotli(quality 9)
3396 # compressed_size: 942M | 869M (~8% reduced) | 854M
3397 # compression_time: 75s | 265s | 719s
3398 # decompression_time: 15s | 25s | 25s
3399
3400 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003401 brotli_cmd = ['brotli', '--quality=6',
3402 '--output={}.new.dat.br'.format(self.path),
3403 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003404 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003405 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003406
3407 new_data_name = '{}.new.dat.br'.format(self.partition)
3408 ZipWrite(output_zip,
3409 '{}.new.dat.br'.format(self.path),
3410 new_data_name,
3411 compress_type=zipfile.ZIP_STORED)
3412 else:
3413 new_data_name = '{}.new.dat'.format(self.partition)
3414 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3415
Dan Albert8e0178d2015-01-27 15:53:15 -08003416 ZipWrite(output_zip,
3417 '{}.patch.dat'.format(self.path),
3418 '{}.patch.dat'.format(self.partition),
3419 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003420
Tianjie Xu209db462016-05-24 17:34:52 -07003421 if self.partition == "system":
3422 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3423 else:
3424 code = ErrorCode.VENDOR_UPDATE_FAILURE
3425
Yifan Hong10c530d2018-12-27 17:34:18 -08003426 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003427 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003428 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003429 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003430 device=self.device, partition=self.partition,
3431 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003432 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003433
Kelvin Zhang0876c412020-06-23 15:06:58 -04003434 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003435 data = source.ReadRangeSet(ranges)
3436 ctx = sha1()
3437
3438 for p in data:
3439 ctx.update(p)
3440
3441 return ctx.hexdigest()
3442
Kelvin Zhang0876c412020-06-23 15:06:58 -04003443 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003444 """Return the hash value for all zero blocks."""
3445 zero_block = '\x00' * 4096
3446 ctx = sha1()
3447 for _ in range(num_blocks):
3448 ctx.update(zero_block)
3449
3450 return ctx.hexdigest()
3451
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003452
Tianjie Xu41976c72019-07-03 13:57:01 -07003453# Expose these two classes to support vendor-specific scripts
3454DataImage = images.DataImage
3455EmptyImage = images.EmptyImage
3456
Tao Bao76def242017-11-21 09:25:31 -08003457
Doug Zongker96a57e72010-09-26 14:57:41 -07003458# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003459PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003460 "ext4": "EMMC",
3461 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003462 "f2fs": "EMMC",
Tim Zimmermannad19ca52022-10-01 11:56:57 +02003463 "squashfs": "EMMC",
3464 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003465}
Doug Zongker96a57e72010-09-26 14:57:41 -07003466
Kelvin Zhang0876c412020-06-23 15:06:58 -04003467
Yifan Hongbdb32012020-05-07 12:38:53 -07003468def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3469 """
3470 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3471 backwards compatibility. It aborts if the fstab entry has slotselect option
3472 (unless check_no_slot is explicitly set to False).
3473 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003474 fstab = info["fstab"]
3475 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003476 if check_no_slot:
3477 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003478 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003479 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3480 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003481 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003482
3483
Yifan Hongbdb32012020-05-07 12:38:53 -07003484def GetTypeAndDeviceExpr(mount_point, info):
3485 """
3486 Return the filesystem of the partition, and an edify expression that evaluates
3487 to the device at runtime.
3488 """
3489 fstab = info["fstab"]
3490 if fstab:
3491 p = fstab[mount_point]
3492 device_expr = '"%s"' % fstab[mount_point].device
3493 if p.slotselect:
3494 device_expr = 'add_slot_suffix(%s)' % device_expr
3495 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003496 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003497
3498
3499def GetEntryForDevice(fstab, device):
3500 """
3501 Returns:
3502 The first entry in fstab whose device is the given value.
3503 """
3504 if not fstab:
3505 return None
3506 for mount_point in fstab:
3507 if fstab[mount_point].device == device:
3508 return fstab[mount_point]
3509 return None
3510
Kelvin Zhang0876c412020-06-23 15:06:58 -04003511
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003512def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003513 """Parses and converts a PEM-encoded certificate into DER-encoded.
3514
3515 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3516
3517 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003518 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003519 """
3520 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003521 save = False
3522 for line in data.split("\n"):
3523 if "--END CERTIFICATE--" in line:
3524 break
3525 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003526 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003527 if "--BEGIN CERTIFICATE--" in line:
3528 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003529 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003530 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003531
Tao Bao04e1f012018-02-04 12:13:35 -08003532
3533def ExtractPublicKey(cert):
3534 """Extracts the public key (PEM-encoded) from the given certificate file.
3535
3536 Args:
3537 cert: The certificate filename.
3538
3539 Returns:
3540 The public key string.
3541
3542 Raises:
3543 AssertionError: On non-zero return from 'openssl'.
3544 """
3545 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3546 # While openssl 1.1 writes the key into the given filename followed by '-out',
3547 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3548 # stdout instead.
3549 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3550 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3551 pubkey, stderrdata = proc.communicate()
3552 assert proc.returncode == 0, \
3553 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3554 return pubkey
3555
3556
Tao Bao1ac886e2019-06-26 11:58:22 -07003557def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003558 """Extracts the AVB public key from the given public or private key.
3559
3560 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003561 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003562 key: The input key file, which should be PEM-encoded public or private key.
3563
3564 Returns:
3565 The path to the extracted AVB public key file.
3566 """
3567 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3568 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003569 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003570 return output
3571
3572
Doug Zongker412c02f2014-02-13 10:58:24 -08003573def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3574 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003575 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003576
Tao Bao6d5d6232018-03-09 17:04:42 -08003577 Most of the space in the boot and recovery images is just the kernel, which is
3578 identical for the two, so the resulting patch should be efficient. Add it to
3579 the output zip, along with a shell script that is run from init.rc on first
3580 boot to actually do the patching and install the new recovery image.
3581
3582 Args:
3583 input_dir: The top-level input directory of the target-files.zip.
3584 output_sink: The callback function that writes the result.
3585 recovery_img: File object for the recovery image.
3586 boot_img: File objects for the boot image.
3587 info_dict: A dict returned by common.LoadInfoDict() on the input
3588 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003589 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003590 if info_dict is None:
3591 info_dict = OPTIONS.info_dict
3592
Tao Bao6d5d6232018-03-09 17:04:42 -08003593 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003594 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
Ricky Cheung7264f022024-03-29 18:55:05 +08003595 board_builds_vendorimage = info_dict.get("board_builds_vendorimage") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003596
Ricky Cheung7264f022024-03-29 18:55:05 +08003597 recovery_img_path = "etc/recovery.img"
3598 if board_builds_vendorimage:
Bill Peckhame868aec2019-09-17 17:06:47 -07003599 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
Ricky Cheung7264f022024-03-29 18:55:05 +08003600 elif not board_uses_vendorimage:
Bill Peckhame868aec2019-09-17 17:06:47 -07003601 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
Ricky Cheung7264f022024-03-29 18:55:05 +08003602 else:
3603 logger.warning('Recovery patch generation is disable when prebuilt vendor image is used.')
3604 return None
Doug Zongkerc9253822014-02-04 12:17:58 -08003605
Tao Baof2cffbd2015-07-22 12:33:18 -07003606 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003607 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003608
3609 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003610 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003611 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003612 # With system-root-image, boot and recovery images will have mismatching
3613 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3614 # to handle such a case.
3615 if system_root_image:
3616 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003617 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003618 assert not os.path.exists(path)
3619 else:
3620 diff_program = ["imgdiff"]
3621 if os.path.exists(path):
3622 diff_program.append("-b")
3623 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003624 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003625 else:
3626 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003627
3628 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3629 _, _, patch = d.ComputePatch()
3630 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003631
Dan Albertebb19aa2015-03-27 19:11:53 -07003632 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003633 # The following GetTypeAndDevice()s need to use the path in the target
3634 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003635 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3636 check_no_slot=False)
3637 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3638 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003639 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003640 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003641
Tao Baof2cffbd2015-07-22 12:33:18 -07003642 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003643
3644 # Note that we use /vendor to refer to the recovery resources. This will
3645 # work for a separate vendor partition mounted at /vendor or a
3646 # /system/vendor subdirectory on the system partition, for which init will
3647 # create a symlink from /vendor to /system/vendor.
3648
3649 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003650if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3651 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003652 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003653 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3654 log -t recovery "Installing new recovery image: succeeded" || \\
3655 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003656else
3657 log -t recovery "Recovery image already installed"
3658fi
3659""" % {'type': recovery_type,
3660 'device': recovery_device,
3661 'sha1': recovery_img.sha1,
3662 'size': recovery_img.size}
3663 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003664 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003665if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3666 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003667 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003668 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3669 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3670 log -t recovery "Installing new recovery image: succeeded" || \\
3671 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003672else
3673 log -t recovery "Recovery image already installed"
3674fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003675""" % {'boot_size': boot_img.size,
3676 'boot_sha1': boot_img.sha1,
3677 'recovery_size': recovery_img.size,
3678 'recovery_sha1': recovery_img.sha1,
3679 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003680 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003681 'recovery_type': recovery_type,
3682 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003683 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003684
Bill Peckhame868aec2019-09-17 17:06:47 -07003685 # The install script location moved from /system/etc to /system/bin in the L
3686 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
dianlujitaoad6e17c2020-09-12 13:48:26 +08003687 output_sink("bin/install-recovery.sh", sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003688
3689
3690class DynamicPartitionUpdate(object):
3691 def __init__(self, src_group=None, tgt_group=None, progress=None,
3692 block_difference=None):
3693 self.src_group = src_group
3694 self.tgt_group = tgt_group
3695 self.progress = progress
3696 self.block_difference = block_difference
3697
3698 @property
3699 def src_size(self):
3700 if not self.block_difference:
3701 return 0
3702 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3703
3704 @property
3705 def tgt_size(self):
3706 if not self.block_difference:
3707 return 0
3708 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3709
3710 @staticmethod
3711 def _GetSparseImageSize(img):
3712 if not img:
3713 return 0
3714 return img.blocksize * img.total_blocks
3715
3716
3717class DynamicGroupUpdate(object):
3718 def __init__(self, src_size=None, tgt_size=None):
3719 # None: group does not exist. 0: no size limits.
3720 self.src_size = src_size
3721 self.tgt_size = tgt_size
3722
3723
3724class DynamicPartitionsDifference(object):
3725 def __init__(self, info_dict, block_diffs, progress_dict=None,
Peter Cai6001dfb2020-03-01 14:43:57 +08003726 source_info_dict=None, build_without_vendor=False):
Yifan Hong10c530d2018-12-27 17:34:18 -08003727 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003728 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003729
Peter Cai6001dfb2020-03-01 14:43:57 +08003730 self._build_without_vendor = build_without_vendor
Yifan Hong10c530d2018-12-27 17:34:18 -08003731 self._remove_all_before_apply = False
3732 if source_info_dict is None:
3733 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003734 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003735
Tao Baof1113e92019-06-18 12:10:14 -07003736 block_diff_dict = collections.OrderedDict(
3737 [(e.partition, e) for e in block_diffs])
3738
Yifan Hong10c530d2018-12-27 17:34:18 -08003739 assert len(block_diff_dict) == len(block_diffs), \
3740 "Duplicated BlockDifference object for {}".format(
3741 [partition for partition, count in
3742 collections.Counter(e.partition for e in block_diffs).items()
3743 if count > 1])
3744
Yifan Hong79997e52019-01-23 16:56:19 -08003745 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003746
3747 for p, block_diff in block_diff_dict.items():
3748 self._partition_updates[p] = DynamicPartitionUpdate()
3749 self._partition_updates[p].block_difference = block_diff
3750
3751 for p, progress in progress_dict.items():
3752 if p in self._partition_updates:
3753 self._partition_updates[p].progress = progress
3754
3755 tgt_groups = shlex.split(info_dict.get(
3756 "super_partition_groups", "").strip())
3757 src_groups = shlex.split(source_info_dict.get(
3758 "super_partition_groups", "").strip())
3759
3760 for g in tgt_groups:
3761 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003762 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003763 assert p in self._partition_updates, \
3764 "{} is in target super_{}_partition_list but no BlockDifference " \
3765 "object is provided.".format(p, g)
3766 self._partition_updates[p].tgt_group = g
3767
3768 for g in src_groups:
3769 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003770 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003771 assert p in self._partition_updates, \
3772 "{} is in source super_{}_partition_list but no BlockDifference " \
3773 "object is provided.".format(p, g)
3774 self._partition_updates[p].src_group = g
3775
Yifan Hong45433e42019-01-18 13:55:25 -08003776 target_dynamic_partitions = set(shlex.split(info_dict.get(
3777 "dynamic_partition_list", "").strip()))
3778 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3779 if u.tgt_size)
3780 assert block_diffs_with_target == target_dynamic_partitions, \
3781 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3782 list(target_dynamic_partitions), list(block_diffs_with_target))
3783
3784 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3785 "dynamic_partition_list", "").strip()))
3786 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3787 if u.src_size)
3788 assert block_diffs_with_source == source_dynamic_partitions, \
3789 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3790 list(source_dynamic_partitions), list(block_diffs_with_source))
3791
Yifan Hong10c530d2018-12-27 17:34:18 -08003792 if self._partition_updates:
3793 logger.info("Updating dynamic partitions %s",
3794 self._partition_updates.keys())
3795
Yifan Hong79997e52019-01-23 16:56:19 -08003796 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003797
3798 for g in tgt_groups:
3799 self._group_updates[g] = DynamicGroupUpdate()
3800 self._group_updates[g].tgt_size = int(info_dict.get(
3801 "super_%s_group_size" % g, "0").strip())
3802
3803 for g in src_groups:
3804 if g not in self._group_updates:
3805 self._group_updates[g] = DynamicGroupUpdate()
3806 self._group_updates[g].src_size = int(source_info_dict.get(
3807 "super_%s_group_size" % g, "0").strip())
3808
3809 self._Compute()
3810
3811 def WriteScript(self, script, output_zip, write_verify_script=False):
3812 script.Comment('--- Start patching dynamic partitions ---')
3813 for p, u in self._partition_updates.items():
3814 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3815 script.Comment('Patch partition %s' % p)
3816 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3817 write_verify_script=False)
3818
3819 op_list_path = MakeTempFile()
3820 with open(op_list_path, 'w') as f:
3821 for line in self._op_list:
3822 f.write('{}\n'.format(line))
3823
3824 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3825
3826 script.Comment('Update dynamic partition metadata')
Alexander Martinz9004c0d2024-08-19 08:47:08 +02003827 script.AppendExtra('assert(update_dynamic_partitions('
3828 'package_extract_file("dynamic_partitions_op_list")));')
Yifan Hong10c530d2018-12-27 17:34:18 -08003829
3830 if write_verify_script:
3831 for p, u in self._partition_updates.items():
3832 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3833 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003834 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003835
3836 for p, u in self._partition_updates.items():
3837 if u.tgt_size and u.src_size <= u.tgt_size:
3838 script.Comment('Patch partition %s' % p)
3839 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3840 write_verify_script=write_verify_script)
3841 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003842 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003843
3844 script.Comment('--- End patching dynamic partitions ---')
3845
3846 def _Compute(self):
3847 self._op_list = list()
3848
3849 def append(line):
3850 self._op_list.append(line)
3851
3852 def comment(line):
3853 self._op_list.append("# %s" % line)
3854
Peter Cai6001dfb2020-03-01 14:43:57 +08003855 if self._build_without_vendor:
3856 comment('System-only build, keep original vendor partition')
3857 # When building without vendor, we do not want to override
3858 # any partition already existing. In this case, we can only
3859 # resize, but not remove / create / re-create any other
3860 # partition.
3861 for p, u in self._partition_updates.items():
3862 comment('Resize partition %s to %s' % (p, u.tgt_size))
3863 append('resize %s %s' % (p, u.tgt_size))
3864 return
3865
Yifan Hong10c530d2018-12-27 17:34:18 -08003866 if self._remove_all_before_apply:
3867 comment('Remove all existing dynamic partitions and groups before '
3868 'applying full OTA')
3869 append('remove_all_groups')
3870
3871 for p, u in self._partition_updates.items():
3872 if u.src_group and not u.tgt_group:
3873 append('remove %s' % p)
3874
3875 for p, u in self._partition_updates.items():
3876 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3877 comment('Move partition %s from %s to default' % (p, u.src_group))
3878 append('move %s default' % p)
3879
3880 for p, u in self._partition_updates.items():
3881 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3882 comment('Shrink partition %s from %d to %d' %
3883 (p, u.src_size, u.tgt_size))
3884 append('resize %s %s' % (p, u.tgt_size))
3885
3886 for g, u in self._group_updates.items():
3887 if u.src_size is not None and u.tgt_size is None:
3888 append('remove_group %s' % g)
3889 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003890 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003891 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3892 append('resize_group %s %d' % (g, u.tgt_size))
3893
3894 for g, u in self._group_updates.items():
3895 if u.src_size is None and u.tgt_size is not None:
3896 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3897 append('add_group %s %d' % (g, u.tgt_size))
3898 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003899 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003900 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3901 append('resize_group %s %d' % (g, u.tgt_size))
3902
3903 for p, u in self._partition_updates.items():
3904 if u.tgt_group and not u.src_group:
3905 comment('Add partition %s to group %s' % (p, u.tgt_group))
3906 append('add %s %s' % (p, u.tgt_group))
3907
3908 for p, u in self._partition_updates.items():
3909 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003910 comment('Grow partition %s from %d to %d' %
3911 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003912 append('resize %s %d' % (p, u.tgt_size))
3913
3914 for p, u in self._partition_updates.items():
3915 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3916 comment('Move partition %s from default to %s' %
3917 (p, u.tgt_group))
3918 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003919
3920
jiajia tangf3f842b2021-03-17 21:49:44 +08003921def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003922 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003923 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003924
3925 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003926 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003927
3928 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003929 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003930 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003931 tmp_dir = MakeTempDir('boot_', suffix='.img')
3932 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003933 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3934 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003935 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3936 if not os.path.isfile(ramdisk):
3937 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3938 return None
3939 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003940 if ramdisk_format == RamdiskFormat.LZ4:
3941 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3942 elif ramdisk_format == RamdiskFormat.GZ:
3943 with open(ramdisk, 'rb') as input_stream:
3944 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003945 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3946 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003947 p2.wait()
Luca Stefani7f2913c2020-06-11 13:03:18 +02003948 elif ramdisk_format == RamdiskFormat.XZ:
3949 with open(ramdisk, 'rb') as input_stream:
3950 with open(uncompressed_ramdisk, 'wb') as output_stream:
3951 p2 = Run(['xz', '-d'], stdin=input_stream.fileno(),
3952 stdout=output_stream.fileno())
3953 p2.wait()
jiajia tangf3f842b2021-03-17 21:49:44 +08003954 else:
Luca Stefani7f2913c2020-06-11 13:03:18 +02003955 logger.error('Only support lz4, xz, or minigzip ramdisk format.')
jiajia tangf3f842b2021-03-17 21:49:44 +08003956 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003957
3958 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3959 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3960 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3961 # the host environment.
3962 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003963 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003964
Yifan Hongc65a0542021-01-07 14:21:01 -08003965 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3966 prop_file = os.path.join(extracted_ramdisk, search_path)
3967 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003968 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003969 logger.warning(
3970 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003971
Yifan Hong7dc51172021-01-12 11:27:39 -08003972 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003973
Yifan Hong85ac5012021-01-07 14:43:46 -08003974 except ExternalError as e:
3975 logger.warning('Unable to get boot image build props: %s', e)
3976 return None
3977
3978
3979def GetBootImageTimestamp(boot_img):
3980 """
3981 Get timestamp from ramdisk within the boot image
3982
3983 Args:
3984 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3985
3986 Return:
3987 An integer that corresponds to the timestamp of the boot image, or None
3988 if file has unknown format. Raise exception if an unexpected error has
3989 occurred.
3990 """
3991 prop_file = GetBootImageBuildProp(boot_img)
3992 if not prop_file:
3993 return None
3994
3995 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3996 if props is None:
3997 return None
3998
3999 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08004000 timestamp = props.GetProp('ro.bootimage.build.date.utc')
4001 if timestamp:
4002 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04004003 logger.warning(
4004 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08004005 return None
4006
4007 except ExternalError as e:
4008 logger.warning('Unable to get boot image timestamp: %s', e)
4009 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004010
4011
4012def GetCareMap(which, imgname):
4013 """Returns the care_map string for the given partition.
4014
4015 Args:
4016 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
4017 imgname: The filename of the image.
4018
4019 Returns:
4020 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
4021 RangeSet; or None.
4022 """
4023 assert which in PARTITIONS_WITH_CARE_MAP
4024
4025 # which + "_image_size" contains the size that the actual filesystem image
4026 # resides in, which is all that needs to be verified. The additional blocks in
4027 # the image file contain verity metadata, by reading which would trigger
4028 # invalid reads.
4029 image_size = OPTIONS.info_dict.get(which + "_image_size")
4030 if not image_size:
4031 return None
4032
David Anderson9e95a022021-08-31 21:32:45 -07004033 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
4034
Kelvin Zhang27324132021-03-22 15:38:38 -04004035 image_blocks = int(image_size) // 4096 - 1
Kelvin Zhang98ef7bb2022-01-07 14:41:46 -08004036 # It's OK for image_blocks to be 0, because care map ranges are inclusive.
4037 # So 0-0 means "just block 0", which is valid.
4038 assert image_blocks >= 0, "blocks for {} must be non-negative, image size: {}".format(
4039 which, image_size)
Kelvin Zhang27324132021-03-22 15:38:38 -04004040
4041 # For sparse images, we will only check the blocks that are listed in the care
4042 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07004043 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04004044 simg = sparse_img.SparseImage(imgname)
4045 care_map_ranges = simg.care_map.intersect(
4046 rangelib.RangeSet("0-{}".format(image_blocks)))
4047
4048 # Otherwise for non-sparse images, we read all the blocks in the filesystem
4049 # image.
4050 else:
4051 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
4052
4053 return [which, care_map_ranges.to_string_raw()]
4054
4055
4056def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
4057 """Generates and adds care_map.pb for a/b partition that has care_map.
4058
4059 Args:
4060 output_file: The output zip file (needs to be already open),
4061 or file path to write care_map.pb.
4062 ab_partitions: The list of A/B partitions.
4063 image_paths: A map from the partition name to the image path.
4064 """
4065 if not output_file:
4066 raise ExternalError('Expected output_file for AddCareMapForAbOta')
4067
4068 care_map_list = []
4069 for partition in ab_partitions:
4070 partition = partition.strip()
4071 if partition not in PARTITIONS_WITH_CARE_MAP:
4072 continue
4073
4074 verity_block_device = "{}_verity_block_device".format(partition)
4075 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
4076 if (verity_block_device in OPTIONS.info_dict or
4077 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
4078 if partition not in image_paths:
4079 logger.warning('Potential partition with care_map missing from images: %s',
4080 partition)
4081 continue
4082 image_path = image_paths[partition]
4083 if not os.path.exists(image_path):
4084 raise ExternalError('Expected image at path {}'.format(image_path))
4085
4086 care_map = GetCareMap(partition, image_path)
4087 if not care_map:
4088 continue
4089 care_map_list += care_map
4090
4091 # adds fingerprint field to the care_map
4092 # TODO(xunchang) revisit the fingerprint calculation for care_map.
4093 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
4094 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
4095 "ro.{}.build.thumbprint".format(partition)]
4096
4097 present_props = [x for x in prop_name_list if
4098 partition_props and partition_props.GetProp(x)]
4099 if not present_props:
4100 logger.warning(
4101 "fingerprint is not present for partition %s", partition)
4102 property_id, fingerprint = "unknown", "unknown"
4103 else:
4104 property_id = present_props[0]
4105 fingerprint = partition_props.GetProp(property_id)
4106 care_map_list += [property_id, fingerprint]
4107
4108 if not care_map_list:
4109 return
4110
4111 # Converts the list into proto buf message by calling care_map_generator; and
4112 # writes the result to a temp file.
4113 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
4114 suffix=".txt")
4115 with open(temp_care_map_text, 'w') as text_file:
4116 text_file.write('\n'.join(care_map_list))
4117
4118 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
4119 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
4120 RunAndCheckOutput(care_map_gen_cmd)
4121
4122 if not isinstance(output_file, zipfile.ZipFile):
4123 shutil.copy(temp_care_map, output_file)
4124 return
4125 # output_file is a zip file
4126 care_map_path = "META/care_map.pb"
4127 if care_map_path in output_file.namelist():
4128 # Copy the temp file into the OPTIONS.input_tmp dir and update the
4129 # replace_updated_files_list used by add_img_to_target_files
4130 if not OPTIONS.replace_updated_files_list:
4131 OPTIONS.replace_updated_files_list = []
4132 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
4133 OPTIONS.replace_updated_files_list.append(care_map_path)
4134 else:
4135 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07004136
4137
4138def IsSparseImage(filepath):
4139 with open(filepath, 'rb') as fp:
4140 # Magic for android sparse image format
4141 # https://source.android.com/devices/bootloader/images
4142 return fp.read(4) == b'\x3A\xFF\x26\xED'