blob: 3c5ba10d6781cb1b26e1a2d0df48ed73f24b1495 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Kelvin Zhang5ef25192022-10-19 11:25:22 -070023from genericpath import isdir
Doug Zongkereef39442009-04-02 12:14:19 -070024import getopt
25import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010026import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070027import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070028import json
29import logging
30import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070031import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080032import platform
Doug Zongkereef39442009-04-02 12:14:19 -070033import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070034import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070035import shutil
36import subprocess
37import sys
38import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070039import threading
40import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070041import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080042from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070043
Tianjie Xu41976c72019-07-03 13:57:01 -070044import images
Kelvin Zhang27324132021-03-22 15:38:38 -040045import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080046import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070047from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070048
Tao Bao32fcdab2018-10-12 10:30:39 -070049logger = logging.getLogger(__name__)
50
Tao Bao986ee862018-10-04 15:46:16 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070053
Dan Albert8b72aef2015-03-23 19:13:21 -070054 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070055 # Set up search path, in order to find framework/ and lib64/. At the time of
56 # running this function, user-supplied search path (`--path`) hasn't been
57 # available. So the value set here is the default, which might be overridden
58 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040059 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070060 if exec_path.endswith('.py'):
61 script_name = os.path.basename(exec_path)
62 # logger hasn't been initialized yet at this point. Use print to output
63 # warnings.
64 print(
65 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040066 'executable -- build and run `{}` directly.'.format(
67 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070068 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040069 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030070
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080072 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
73 if "ANDROID_HOST_OUT" in os.environ:
74 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080075 self.signapk_shared_library_path = "lib64" # Relative to search_path
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020076 self.sign_sepolicy_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.extra_signapk_args = []
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020078 self.extra_sign_sepolicy_args = []
Martin Stjernholm58472e82022-01-07 22:08:47 +000079 self.aapt2_path = "aapt2"
Dan Albert8b72aef2015-03-23 19:13:21 -070080 self.java_path = "java" # Use the one on the path by default.
Sorin Basca05085832022-09-14 11:33:22 +010081 self.java_args = ["-Xmx4096m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080082 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070083 self.public_key_suffix = ".x509.pem"
84 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070085 # use otatools built boot_signer by default
Dan Albert8b72aef2015-03-23 19:13:21 -070086 self.verbose = False
87 self.tempfiles = []
88 self.device_specific = None
89 self.extras = {}
90 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070091 self.source_info_dict = None
92 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070093 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070094 # Stash size cannot exceed cache_size * threshold.
95 self.cache_size = None
96 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070097 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070098 self.host_tools = {}
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +020099 self.sepolicy_name = 'sepolicy.apex'
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800112# descriptor into vbmeta.img. When adding a new entry here, the
113# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
114# accordingly.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000115AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Lucas Wei03230252022-04-18 16:00:40 +0800116 'system', 'system_ext', 'vendor', 'vendor_boot', 'vendor_kernel_boot',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000117 'vendor_dlkm', 'odm_dlkm', 'system_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800118
Tao Bao08c190f2019-06-03 23:07:58 -0700119# Chained VBMeta partitions.
120AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
121
Tianjie Xu861f4132018-09-12 11:49:33 -0700122# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400123PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700124 'system',
125 'vendor',
126 'product',
127 'system_ext',
128 'odm',
129 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700130 'odm_dlkm',
Ramji Jiyani13a41372022-01-27 07:05:08 +0000131 'system_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhang563750f2021-04-28 12:46:17 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhang563750f2021-04-28 12:46:17 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhang563750f2021-04-28 12:46:17 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Kelvin Zhang766eea72021-06-03 09:36:08 -0400279 if verbose is None:
280 verbose = OPTIONS.verbose
281
Tao Bao32fcdab2018-10-12 10:30:39 -0700282 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400283 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700285 return subprocess.Popen(args, **kwargs)
286
287
Tao Bao986ee862018-10-04 15:46:16 -0700288def RunAndCheckOutput(args, verbose=None, **kwargs):
289 """Runs the given command and returns the output.
290
291 Args:
292 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700293 verbose: Whether the commands should be shown. Default to the global
294 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700295 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
296 stdin, etc. stdout and stderr will default to subprocess.PIPE and
297 subprocess.STDOUT respectively unless caller specifies any of them.
298
299 Returns:
300 The output string.
301
302 Raises:
303 ExternalError: On non-zero exit from the command.
304 """
Tao Bao986ee862018-10-04 15:46:16 -0700305 proc = Run(args, verbose=verbose, **kwargs)
306 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800307 if output is None:
308 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700309 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400310 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700311 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700312 if proc.returncode != 0:
313 raise ExternalError(
314 "Failed to run command '{}' (exit code {}):\n{}".format(
315 args, proc.returncode, output))
316 return output
317
318
Tao Baoc765cca2018-01-31 17:32:40 -0800319def RoundUpTo4K(value):
320 rounded_up = value + 4095
321 return rounded_up - (rounded_up % 4096)
322
323
Ying Wang7e6d4e42010-12-13 16:25:36 -0800324def CloseInheritedPipes():
325 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
326 before doing other work."""
327 if platform.system() != "Darwin":
328 return
329 for d in range(3, 1025):
330 try:
331 stat = os.fstat(d)
332 if stat is not None:
333 pipebit = stat[0] & 0x1000
334 if pipebit != 0:
335 os.close(d)
336 except OSError:
337 pass
338
339
Tao Bao1c320f82019-10-04 23:25:12 -0700340class BuildInfo(object):
341 """A class that holds the information for a given build.
342
343 This class wraps up the property querying for a given source or target build.
344 It abstracts away the logic of handling OEM-specific properties, and caches
345 the commonly used properties such as fingerprint.
346
347 There are two types of info dicts: a) build-time info dict, which is generated
348 at build time (i.e. included in a target_files zip); b) OEM info dict that is
349 specified at package generation time (via command line argument
350 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
351 having "oem_fingerprint_properties" in build-time info dict), all the queries
352 would be answered based on build-time info dict only. Otherwise if using
353 OEM-specific properties, some of them will be calculated from two info dicts.
354
355 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800356 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700357
358 Attributes:
359 info_dict: The build-time info dict.
360 is_ab: Whether it's a build that uses A/B OTA.
361 oem_dicts: A list of OEM dicts.
362 oem_props: A list of OEM properties that should be read from OEM dicts; None
363 if the build doesn't use any OEM-specific property.
364 fingerprint: The fingerprint of the build, which would be calculated based
365 on OEM properties if applicable.
366 device: The device name, which could come from OEM dicts if applicable.
367 """
368
369 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
370 "ro.product.manufacturer", "ro.product.model",
371 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700372 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
373 "product", "odm", "vendor", "system_ext", "system"]
374 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
375 "product", "product_services", "odm", "vendor", "system"]
376 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700377
Tianjiefdda51d2021-05-05 14:46:35 -0700378 # The length of vbmeta digest to append to the fingerprint
379 _VBMETA_DIGEST_SIZE_USED = 8
380
381 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700382 """Initializes a BuildInfo instance with the given dicts.
383
384 Note that it only wraps up the given dicts, without making copies.
385
386 Arguments:
387 info_dict: The build-time info dict.
388 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
389 that it always uses the first dict to calculate the fingerprint or the
390 device name. The rest would be used for asserting OEM properties only
391 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700392 use_legacy_id: Use the legacy build id to construct the fingerprint. This
393 is used when we need a BuildInfo class, while the vbmeta digest is
394 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700395
396 Raises:
397 ValueError: On invalid inputs.
398 """
399 self.info_dict = info_dict
400 self.oem_dicts = oem_dicts
401
402 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700403 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700404
Hongguang Chend7c160f2020-05-03 21:24:26 -0700405 # Skip _oem_props if oem_dicts is None to use BuildInfo in
406 # sign_target_files_apks
407 if self.oem_dicts:
408 self._oem_props = info_dict.get("oem_fingerprint_properties")
409 else:
410 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700411
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 def check_fingerprint(fingerprint):
413 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
414 raise ValueError(
415 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
416 "3.2.2. Build Parameters.".format(fingerprint))
417
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800419 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800420 try:
421 fingerprint = self.CalculatePartitionFingerprint(partition)
422 check_fingerprint(fingerprint)
423 self._partition_fingerprints[partition] = fingerprint
424 except ExternalError:
425 continue
426 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800427 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800428 # need a fingerprint when creating the image.
429 self._partition_fingerprints[
430 "system_other"] = self._partition_fingerprints["system"]
431
Tao Bao1c320f82019-10-04 23:25:12 -0700432 # These two should be computed only after setting self._oem_props.
433 self._device = self.GetOemProperty("ro.product.device")
434 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700436
437 @property
438 def is_ab(self):
439 return self._is_ab
440
441 @property
442 def device(self):
443 return self._device
444
445 @property
446 def fingerprint(self):
447 return self._fingerprint
448
449 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400450 def is_vabc(self):
451 vendor_prop = self.info_dict.get("vendor.build.prop")
452 vabc_enabled = vendor_prop and \
453 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
454 return vabc_enabled
455
456 @property
Kelvin Zhanga9a87ec2022-05-04 16:44:52 -0700457 def is_android_r(self):
458 system_prop = self.info_dict.get("system.build.prop")
459 return system_prop and system_prop.GetProp("ro.build.version.release") == "11"
460
461 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700462 def is_vabc_xor(self):
463 vendor_prop = self.info_dict.get("vendor.build.prop")
464 vabc_xor_enabled = vendor_prop and \
465 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
466 return vabc_xor_enabled
467
468 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400469 def vendor_suppressed_vabc(self):
470 vendor_prop = self.info_dict.get("vendor.build.prop")
471 vabc_suppressed = vendor_prop and \
472 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
473 return vabc_suppressed and vabc_suppressed.lower() == "true"
474
475 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def oem_props(self):
477 return self._oem_props
478
479 def __getitem__(self, key):
480 return self.info_dict[key]
481
482 def __setitem__(self, key, value):
483 self.info_dict[key] = value
484
485 def get(self, key, default=None):
486 return self.info_dict.get(key, default)
487
488 def items(self):
489 return self.info_dict.items()
490
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 def _GetRawBuildProp(self, prop, partition):
492 prop_file = '{}.build.prop'.format(
493 partition) if partition else 'build.prop'
494 partition_props = self.info_dict.get(prop_file)
495 if not partition_props:
496 return None
497 return partition_props.GetProp(prop)
498
Daniel Normand5fe8622020-01-08 17:01:11 -0800499 def GetPartitionBuildProp(self, prop, partition):
500 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800501
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000502 # Boot image and init_boot image uses ro.[product.]bootimage instead of boot.
Devin Mooreb5195ff2022-02-11 18:44:26 +0000503 # This comes from the generic ramdisk
Kelvin Zhang8250d2c2022-03-23 19:46:09 +0000504 prop_partition = "bootimage" if partition == "boot" or partition == "init_boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800505
Daniel Normand5fe8622020-01-08 17:01:11 -0800506 # If provided a partition for this property, only look within that
507 # partition's build.prop.
508 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800509 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800510 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800511 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512
513 prop_val = self._GetRawBuildProp(prop, partition)
514 if prop_val is not None:
515 return prop_val
516 raise ExternalError("couldn't find %s in %s.build.prop" %
517 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800518
Tao Bao1c320f82019-10-04 23:25:12 -0700519 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700521 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
522 return self._ResolveRoProductBuildProp(prop)
523
Tianjiefdda51d2021-05-05 14:46:35 -0700524 if prop == "ro.build.id":
525 return self._GetBuildId()
526
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000527 prop_val = self._GetRawBuildProp(prop, None)
528 if prop_val is not None:
529 return prop_val
530
531 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700532
533 def _ResolveRoProductBuildProp(self, prop):
534 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000535 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700536 if prop_val:
537 return prop_val
538
Steven Laver8e2086e2020-04-27 16:26:31 -0700539 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000540 source_order_val = self._GetRawBuildProp(
541 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700542 if source_order_val:
543 source_order = source_order_val.split(",")
544 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700545 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700546
547 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700548 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700549 raise ExternalError(
550 "Invalid ro.product.property_source_order '{}'".format(source_order))
551
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000552 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700553 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000554 "ro.product", "ro.product.{}".format(source_partition), 1)
555 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700556 if prop_val:
557 return prop_val
558
559 raise ExternalError("couldn't resolve {}".format(prop))
560
Steven Laver8e2086e2020-04-27 16:26:31 -0700561 def _GetRoProductPropsDefaultSourceOrder(self):
562 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
563 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000564 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700565 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000566 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700567 if android_version == "10":
568 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
569 # NOTE: float() conversion of android_version will have rounding error.
570 # We are checking for "9" or less, and using "< 10" is well outside of
571 # possible floating point rounding.
572 try:
573 android_version_val = float(android_version)
574 except ValueError:
575 android_version_val = 0
576 if android_version_val < 10:
577 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
578 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
579
Tianjieb37c5be2020-10-15 21:27:10 -0700580 def _GetPlatformVersion(self):
581 version_sdk = self.GetBuildProp("ro.build.version.sdk")
582 # init code switches to version_release_or_codename (see b/158483506). After
583 # API finalization, release_or_codename will be the same as release. This
584 # is the best effort to support pre-S dev stage builds.
585 if int(version_sdk) >= 30:
586 try:
587 return self.GetBuildProp("ro.build.version.release_or_codename")
588 except ExternalError:
589 logger.warning('Failed to find ro.build.version.release_or_codename')
590
591 return self.GetBuildProp("ro.build.version.release")
592
Tianjiefdda51d2021-05-05 14:46:35 -0700593 def _GetBuildId(self):
594 build_id = self._GetRawBuildProp("ro.build.id", None)
595 if build_id:
596 return build_id
597
598 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
599 if not legacy_build_id:
600 raise ExternalError("Couldn't find build id in property file")
601
602 if self.use_legacy_id:
603 return legacy_build_id
604
605 # Append the top 8 chars of vbmeta digest to the existing build id. The
606 # logic needs to match the one in init, so that OTA can deliver correctly.
607 avb_enable = self.info_dict.get("avb_enable") == "true"
608 if not avb_enable:
609 raise ExternalError("AVB isn't enabled when using legacy build id")
610
611 vbmeta_digest = self.info_dict.get("vbmeta_digest")
612 if not vbmeta_digest:
613 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
614 " id")
615 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
616 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
617
618 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
619 return legacy_build_id + '.' + digest_prefix
620
Tianjieb37c5be2020-10-15 21:27:10 -0700621 def _GetPartitionPlatformVersion(self, partition):
622 try:
623 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
624 partition)
625 except ExternalError:
626 return self.GetPartitionBuildProp("ro.build.version.release",
627 partition)
628
Tao Bao1c320f82019-10-04 23:25:12 -0700629 def GetOemProperty(self, key):
630 if self.oem_props is not None and key in self.oem_props:
631 return self.oem_dicts[0][key]
632 return self.GetBuildProp(key)
633
Daniel Normand5fe8622020-01-08 17:01:11 -0800634 def GetPartitionFingerprint(self, partition):
635 return self._partition_fingerprints.get(partition, None)
636
637 def CalculatePartitionFingerprint(self, partition):
638 try:
639 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
640 except ExternalError:
641 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
642 self.GetPartitionBuildProp("ro.product.brand", partition),
643 self.GetPartitionBuildProp("ro.product.name", partition),
644 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700645 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800646 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400647 self.GetPartitionBuildProp(
648 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800649 self.GetPartitionBuildProp("ro.build.type", partition),
650 self.GetPartitionBuildProp("ro.build.tags", partition))
651
Tao Bao1c320f82019-10-04 23:25:12 -0700652 def CalculateFingerprint(self):
653 if self.oem_props is None:
654 try:
655 return self.GetBuildProp("ro.build.fingerprint")
656 except ExternalError:
657 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
658 self.GetBuildProp("ro.product.brand"),
659 self.GetBuildProp("ro.product.name"),
660 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700661 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700662 self.GetBuildProp("ro.build.id"),
663 self.GetBuildProp("ro.build.version.incremental"),
664 self.GetBuildProp("ro.build.type"),
665 self.GetBuildProp("ro.build.tags"))
666 return "%s/%s/%s:%s" % (
667 self.GetOemProperty("ro.product.brand"),
668 self.GetOemProperty("ro.product.name"),
669 self.GetOemProperty("ro.product.device"),
670 self.GetBuildProp("ro.build.thumbprint"))
671
672 def WriteMountOemScript(self, script):
673 assert self.oem_props is not None
674 recovery_mount_options = self.info_dict.get("recovery_mount_options")
675 script.Mount("/oem", recovery_mount_options)
676
677 def WriteDeviceAssertions(self, script, oem_no_mount):
678 # Read the property directly if not using OEM properties.
679 if not self.oem_props:
680 script.AssertDevice(self.device)
681 return
682
683 # Otherwise assert OEM properties.
684 if not self.oem_dicts:
685 raise ExternalError(
686 "No OEM file provided to answer expected assertions")
687
688 for prop in self.oem_props.split():
689 values = []
690 for oem_dict in self.oem_dicts:
691 if prop in oem_dict:
692 values.append(oem_dict[prop])
693 if not values:
694 raise ExternalError(
695 "The OEM file is missing the property %s" % (prop,))
696 script.AssertOemProperty(prop, values, oem_no_mount)
697
698
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000699def ReadFromInputFile(input_file, fn):
700 """Reads the contents of fn from input zipfile or directory."""
701 if isinstance(input_file, zipfile.ZipFile):
702 return input_file.read(fn).decode()
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700703 elif zipfile.is_zipfile(input_file):
704 with zipfile.ZipFile(input_file, "r", allowZip64=True) as zfp:
705 return zfp.read(fn).decode()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000706 else:
Kelvin Zhang5ef25192022-10-19 11:25:22 -0700707 if not os.path.isdir(input_file):
708 raise ValueError(
709 "Invalid input_file, accepted inputs are ZipFile object, path to .zip file on disk, or path to extracted directory. Actual: " + input_file)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000710 path = os.path.join(input_file, *fn.split("/"))
711 try:
712 with open(path) as f:
713 return f.read()
714 except IOError as e:
715 if e.errno == errno.ENOENT:
716 raise KeyError(fn)
717
718
Yifan Hong10482a22021-01-07 14:38:41 -0800719def ExtractFromInputFile(input_file, fn):
720 """Extracts the contents of fn from input zipfile or directory into a file."""
721 if isinstance(input_file, zipfile.ZipFile):
722 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500723 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800724 f.write(input_file.read(fn))
725 return tmp_file
726 else:
727 file = os.path.join(input_file, *fn.split("/"))
728 if not os.path.exists(file):
729 raise KeyError(fn)
730 return file
731
Kelvin Zhang563750f2021-04-28 12:46:17 -0400732
jiajia tangf3f842b2021-03-17 21:49:44 +0800733class RamdiskFormat(object):
734 LZ4 = 1
735 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800736
Kelvin Zhang563750f2021-04-28 12:46:17 -0400737
TJ Rhoades6f488e92022-05-01 22:16:22 -0700738def GetRamdiskFormat(info_dict):
jiajia tang836f76b2021-04-02 14:48:26 +0800739 if info_dict.get('lz4_ramdisks') == 'true':
740 ramdisk_format = RamdiskFormat.LZ4
741 else:
742 ramdisk_format = RamdiskFormat.GZ
743 return ramdisk_format
744
Kelvin Zhang563750f2021-04-28 12:46:17 -0400745
Tao Bao410ad8b2018-08-24 12:08:38 -0700746def LoadInfoDict(input_file, repacking=False):
747 """Loads the key/value pairs from the given input target_files.
748
Tianjiea85bdf02020-07-29 11:56:19 -0700749 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700750 checks and returns the parsed key/value pairs for to the given build. It's
751 usually called early when working on input target_files files, e.g. when
752 generating OTAs, or signing builds. Note that the function may be called
753 against an old target_files file (i.e. from past dessert releases). So the
754 property parsing needs to be backward compatible.
755
756 In a `META/misc_info.txt`, a few properties are stored as links to the files
757 in the PRODUCT_OUT directory. It works fine with the build system. However,
758 they are no longer available when (re)generating images from target_files zip.
759 When `repacking` is True, redirect these properties to the actual files in the
760 unzipped directory.
761
762 Args:
763 input_file: The input target_files file, which could be an open
764 zipfile.ZipFile instance, or a str for the dir that contains the files
765 unzipped from a target_files file.
766 repacking: Whether it's trying repack an target_files file after loading the
767 info dict (default: False). If so, it will rewrite a few loaded
768 properties (e.g. selinux_fc, root_dir) to point to the actual files in
769 target_files file. When doing repacking, `input_file` must be a dir.
770
771 Returns:
772 A dict that contains the parsed key/value pairs.
773
774 Raises:
775 AssertionError: On invalid input arguments.
776 ValueError: On malformed input values.
777 """
778 if repacking:
779 assert isinstance(input_file, str), \
780 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700781
Doug Zongkerc9253822014-02-04 12:17:58 -0800782 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000783 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800784
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700785 try:
Michael Runge6e836112014-04-15 17:40:21 -0700786 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700787 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700788 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700789
Tao Bao410ad8b2018-08-24 12:08:38 -0700790 if "recovery_api_version" not in d:
791 raise ValueError("Failed to find 'recovery_api_version'")
792 if "fstab_version" not in d:
793 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800794
Tao Bao410ad8b2018-08-24 12:08:38 -0700795 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700796 # "selinux_fc" properties should point to the file_contexts files
797 # (file_contexts.bin) under META/.
798 for key in d:
799 if key.endswith("selinux_fc"):
800 fc_basename = os.path.basename(d[key])
801 fc_config = os.path.join(input_file, "META", fc_basename)
802 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700803
Daniel Norman72c626f2019-05-13 15:58:14 -0700804 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700805
Tom Cherryd14b8952018-08-09 14:26:00 -0700806 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700807 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700808 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700809 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700810
David Anderson0ec64ac2019-12-06 12:21:18 -0800811 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700812 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Ramji Jiyani13a41372022-01-27 07:05:08 +0000813 "vendor_dlkm", "odm_dlkm", "system_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800814 key_name = part_name + "_base_fs_file"
815 if key_name not in d:
816 continue
817 basename = os.path.basename(d[key_name])
818 base_fs_file = os.path.join(input_file, "META", basename)
819 if os.path.exists(base_fs_file):
820 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700821 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700822 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800823 "Failed to find %s base fs file: %s", part_name, base_fs_file)
824 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700825
Doug Zongker37974732010-09-16 17:44:38 -0700826 def makeint(key):
827 if key in d:
828 d[key] = int(d[key], 0)
829
830 makeint("recovery_api_version")
831 makeint("blocksize")
832 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700833 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700834 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700835 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700836 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800837 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700838
Steve Muckle903a1ca2020-05-07 17:32:10 -0700839 boot_images = "boot.img"
840 if "boot_images" in d:
841 boot_images = d["boot_images"]
842 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400843 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700844
Tao Bao765668f2019-10-04 22:03:00 -0700845 # Load recovery fstab if applicable.
846 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
TJ Rhoades6f488e92022-05-01 22:16:22 -0700847 ramdisk_format = GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800848
Tianjie Xu861f4132018-09-12 11:49:33 -0700849 # Tries to load the build props for all partitions with care_map, including
850 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800851 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800852 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000853 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800854 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700855 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800856
Tao Bao3ed35d32019-10-07 20:48:48 -0700857 # Set up the salt (based on fingerprint) that will be used when adding AVB
858 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800859 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700860 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800861 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800862 fingerprint = build_info.GetPartitionFingerprint(partition)
863 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400864 d["avb_{}_salt".format(partition)] = sha256(
865 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700866
Yong Ma253b1062022-08-18 09:53:27 +0000867 # Set up the salt for partitions without build.prop
868 if build_info.fingerprint:
869 d["avb_salt"] = sha256(build_info.fingerprint.encode()).hexdigest()
870
Tianjiefdda51d2021-05-05 14:46:35 -0700871 # Set the vbmeta digest if exists
872 try:
873 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
874 except KeyError:
875 pass
876
Kelvin Zhang39aea442020-08-17 11:04:25 -0400877 try:
878 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
879 except KeyError:
880 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700881 return d
882
Tao Baod1de6f32017-03-01 16:38:48 -0800883
Daniel Norman4cc9df62019-07-18 10:11:07 -0700884def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900885 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700886 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900887
Daniel Norman4cc9df62019-07-18 10:11:07 -0700888
889def LoadDictionaryFromFile(file_path):
890 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900891 return LoadDictionaryFromLines(lines)
892
893
Michael Runge6e836112014-04-15 17:40:21 -0700894def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700895 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700896 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700897 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700898 if not line or line.startswith("#"):
899 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700900 if "=" in line:
901 name, value = line.split("=", 1)
902 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700903 return d
904
Tao Baod1de6f32017-03-01 16:38:48 -0800905
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000906class PartitionBuildProps(object):
907 """The class holds the build prop of a particular partition.
908
909 This class loads the build.prop and holds the build properties for a given
910 partition. It also partially recognizes the 'import' statement in the
911 build.prop; and calculates alternative values of some specific build
912 properties during runtime.
913
914 Attributes:
915 input_file: a zipped target-file or an unzipped target-file directory.
916 partition: name of the partition.
917 props_allow_override: a list of build properties to search for the
918 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000919 build_props: a dict of build properties for the given partition.
920 prop_overrides: a set of props that are overridden by import.
921 placeholder_values: A dict of runtime variables' values to replace the
922 placeholders in the build.prop file. We expect exactly one value for
923 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800924 ramdisk_format: If name is "boot", the format of ramdisk inside the
925 boot image. Otherwise, its value is ignored.
926 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000927 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400928
Tianjie Xu9afb2212020-05-10 21:48:15 +0000929 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000930 self.input_file = input_file
931 self.partition = name
932 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000933 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000934 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000935 self.prop_overrides = set()
936 self.placeholder_values = {}
937 if placeholder_values:
938 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000939
940 @staticmethod
941 def FromDictionary(name, build_props):
942 """Constructs an instance from a build prop dictionary."""
943
944 props = PartitionBuildProps("unknown", name)
945 props.build_props = build_props.copy()
946 return props
947
948 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800949 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000950 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800951
Devin Mooreafdd7c72021-12-13 22:04:08 +0000952 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400953 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000954 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800955 else:
956 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
957
958 props = PartitionBuildProps(input_file, name, placeholder_values)
959 props._LoadBuildProp(data)
960 return props
961
962 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000963 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800964 """
965 Read build.prop for boot image from input_file.
966 Return empty string if not found.
967 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000968 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800969 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000970 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800971 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000972 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800973 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800974 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800975 if prop_file is None:
976 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500977 with open(prop_file, "r") as f:
978 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800979
980 @staticmethod
981 def _ReadPartitionPropFile(input_file, name):
982 """
983 Read build.prop for name from input_file.
984 Return empty string if not found.
985 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000986 data = ''
987 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
988 '{}/build.prop'.format(name.upper())]:
989 try:
990 data = ReadFromInputFile(input_file, prop_file)
991 break
992 except KeyError:
993 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800994 if data == '':
995 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800996 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000997
Yifan Hong125d0b62020-09-24 17:07:03 -0700998 @staticmethod
999 def FromBuildPropFile(name, build_prop_file):
1000 """Constructs an instance from a build prop file."""
1001
1002 props = PartitionBuildProps("unknown", name)
1003 with open(build_prop_file) as f:
1004 props._LoadBuildProp(f.read())
1005 return props
1006
Tianjie Xu9afb2212020-05-10 21:48:15 +00001007 def _LoadBuildProp(self, data):
1008 for line in data.split('\n'):
1009 line = line.strip()
1010 if not line or line.startswith("#"):
1011 continue
1012 if line.startswith("import"):
1013 overrides = self._ImportParser(line)
1014 duplicates = self.prop_overrides.intersection(overrides.keys())
1015 if duplicates:
1016 raise ValueError('prop {} is overridden multiple times'.format(
1017 ','.join(duplicates)))
1018 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1019 self.build_props.update(overrides)
1020 elif "=" in line:
1021 name, value = line.split("=", 1)
1022 if name in self.prop_overrides:
1023 raise ValueError('prop {} is set again after overridden by import '
1024 'statement'.format(name))
1025 self.build_props[name] = value
1026
1027 def _ImportParser(self, line):
1028 """Parses the build prop in a given import statement."""
1029
1030 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001031 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001032 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001033
1034 if len(tokens) == 3:
1035 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1036 return {}
1037
Tianjie Xu9afb2212020-05-10 21:48:15 +00001038 import_path = tokens[1]
1039 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
Kelvin Zhang42ab8282022-02-17 13:07:55 -08001040 logger.warn('Unrecognized import path {}'.format(line))
1041 return {}
Tianjie Xu9afb2212020-05-10 21:48:15 +00001042
1043 # We only recognize a subset of import statement that the init process
1044 # supports. And we can loose the restriction based on how the dynamic
1045 # fingerprint is used in practice. The placeholder format should be
1046 # ${placeholder}, and its value should be provided by the caller through
1047 # the placeholder_values.
1048 for prop, value in self.placeholder_values.items():
1049 prop_place_holder = '${{{}}}'.format(prop)
1050 if prop_place_holder in import_path:
1051 import_path = import_path.replace(prop_place_holder, value)
1052 if '$' in import_path:
1053 logger.info('Unresolved place holder in import path %s', import_path)
1054 return {}
1055
1056 import_path = import_path.replace('/{}'.format(self.partition),
1057 self.partition.upper())
1058 logger.info('Parsing build props override from %s', import_path)
1059
1060 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1061 d = LoadDictionaryFromLines(lines)
1062 return {key: val for key, val in d.items()
1063 if key in self.props_allow_override}
1064
Kelvin Zhang5ef25192022-10-19 11:25:22 -07001065 def __getstate__(self):
1066 state = self.__dict__.copy()
1067 # Don't pickle baz
1068 if "input_file" in state and isinstance(state["input_file"], zipfile.ZipFile):
1069 state["input_file"] = state["input_file"].filename
1070 return state
1071
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001072 def GetProp(self, prop):
1073 return self.build_props.get(prop)
1074
1075
Tianjie Xucfa86222016-03-07 16:31:19 -08001076def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1077 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001078 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001079 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001080 self.mount_point = mount_point
1081 self.fs_type = fs_type
1082 self.device = device
1083 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001084 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001085 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001086
1087 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001088 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001089 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001090 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001091 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001092
Tao Baod1de6f32017-03-01 16:38:48 -08001093 assert fstab_version == 2
1094
1095 d = {}
1096 for line in data.split("\n"):
1097 line = line.strip()
1098 if not line or line.startswith("#"):
1099 continue
1100
1101 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1102 pieces = line.split()
1103 if len(pieces) != 5:
1104 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1105
1106 # Ignore entries that are managed by vold.
1107 options = pieces[4]
1108 if "voldmanaged=" in options:
1109 continue
1110
1111 # It's a good line, parse it.
1112 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001113 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001114 options = options.split(",")
1115 for i in options:
1116 if i.startswith("length="):
1117 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001118 elif i == "slotselect":
1119 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001120 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001121 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001122 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001123
Tao Baod1de6f32017-03-01 16:38:48 -08001124 mount_flags = pieces[3]
1125 # Honor the SELinux context if present.
1126 context = None
1127 for i in mount_flags.split(","):
1128 if i.startswith("context="):
1129 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001130
Tao Baod1de6f32017-03-01 16:38:48 -08001131 mount_point = pieces[1]
1132 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001133 device=pieces[0], length=length, context=context,
1134 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001135
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001136 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001137 # system. Other areas assume system is always at "/system" so point /system
1138 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001139 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001140 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001141 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001142 return d
1143
1144
Tao Bao765668f2019-10-04 22:03:00 -07001145def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1146 """Finds the path to recovery fstab and loads its contents."""
1147 # recovery fstab is only meaningful when installing an update via recovery
1148 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001149 if info_dict.get('ab_update') == 'true' and \
1150 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001151 return None
1152
1153 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1154 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1155 # cases, since it may load the info_dict from an old build (e.g. when
1156 # generating incremental OTAs from that build).
1157 system_root_image = info_dict.get('system_root_image') == 'true'
1158 if info_dict.get('no_recovery') != 'true':
1159 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1160 if isinstance(input_file, zipfile.ZipFile):
1161 if recovery_fstab_path not in input_file.namelist():
1162 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1163 else:
1164 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1165 if not os.path.exists(path):
1166 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1167 return LoadRecoveryFSTab(
1168 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1169 system_root_image)
1170
1171 if info_dict.get('recovery_as_boot') == 'true':
1172 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1173 if isinstance(input_file, zipfile.ZipFile):
1174 if recovery_fstab_path not in input_file.namelist():
1175 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1176 else:
1177 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1178 if not os.path.exists(path):
1179 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1180 return LoadRecoveryFSTab(
1181 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1182 system_root_image)
1183
1184 return None
1185
1186
Doug Zongker37974732010-09-16 17:44:38 -07001187def DumpInfoDict(d):
1188 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001189 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001190
Dan Albert8b72aef2015-03-23 19:13:21 -07001191
Daniel Norman55417142019-11-25 16:04:36 -08001192def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001193 """Merges dynamic partition info variables.
1194
1195 Args:
1196 framework_dict: The dictionary of dynamic partition info variables from the
1197 partial framework target files.
1198 vendor_dict: The dictionary of dynamic partition info variables from the
1199 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001200
1201 Returns:
1202 The merged dynamic partition info dictionary.
1203 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001204
1205 def uniq_concat(a, b):
jiajia tange5ddfcd2022-06-21 10:36:12 +08001206 combined = set(a.split())
1207 combined.update(set(b.split()))
Daniel Normanb0c75912020-09-24 14:30:21 -07001208 combined = [item.strip() for item in combined if item.strip()]
1209 return " ".join(sorted(combined))
1210
1211 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangf294c872022-10-06 14:21:36 -07001212 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001213 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1214
1215 merged_dict = {"use_dynamic_partitions": "true"}
Kelvin Zhang6a683ce2022-05-02 12:19:45 -07001216 # For keys-value pairs that are the same, copy to merged dict
1217 for key in vendor_dict.keys():
1218 if key in framework_dict and framework_dict[key] == vendor_dict[key]:
1219 merged_dict[key] = vendor_dict[key]
Daniel Normanb0c75912020-09-24 14:30:21 -07001220
1221 merged_dict["dynamic_partition_list"] = uniq_concat(
1222 framework_dict.get("dynamic_partition_list", ""),
1223 vendor_dict.get("dynamic_partition_list", ""))
1224
1225 # Super block devices are defined by the vendor dict.
1226 if "super_block_devices" in vendor_dict:
1227 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001228 for block_device in merged_dict["super_block_devices"].split():
Daniel Normanb0c75912020-09-24 14:30:21 -07001229 key = "super_%s_device_size" % block_device
1230 if key not in vendor_dict:
1231 raise ValueError("Vendor dict does not contain required key %s." % key)
1232 merged_dict[key] = vendor_dict[key]
1233
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001234 # Partition groups and group sizes are defined by the vendor dict because
1235 # these values may vary for each board that uses a shared system image.
1236 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
jiajia tange5ddfcd2022-06-21 10:36:12 +08001237 for partition_group in merged_dict["super_partition_groups"].split():
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001238 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001239 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001240 if key not in vendor_dict:
1241 raise ValueError("Vendor dict does not contain required key %s." % key)
1242 merged_dict[key] = vendor_dict[key]
1243
1244 # Set the partition group's partition list using a concatenation of the
1245 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001246 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001247 merged_dict[key] = uniq_concat(
1248 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301249
Daniel Normanb0c75912020-09-24 14:30:21 -07001250 # Various other flags should be copied from the vendor dict, if defined.
1251 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1252 "super_metadata_device", "super_partition_error_limit",
1253 "super_partition_size"):
1254 if key in vendor_dict.keys():
1255 merged_dict[key] = vendor_dict[key]
1256
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001257 return merged_dict
1258
1259
Daniel Norman21c34f72020-11-11 17:25:50 -08001260def PartitionMapFromTargetFiles(target_files_dir):
1261 """Builds a map from partition -> path within an extracted target files directory."""
1262 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1263 possible_subdirs = {
1264 "system": ["SYSTEM"],
1265 "vendor": ["VENDOR", "SYSTEM/vendor"],
1266 "product": ["PRODUCT", "SYSTEM/product"],
1267 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1268 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1269 "vendor_dlkm": [
1270 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1271 ],
1272 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
Ramji Jiyani13a41372022-01-27 07:05:08 +00001273 "system_dlkm": ["SYSTEM_DLKM", "SYSTEM/system_dlkm"],
Daniel Norman21c34f72020-11-11 17:25:50 -08001274 }
1275 partition_map = {}
1276 for partition, subdirs in possible_subdirs.items():
1277 for subdir in subdirs:
1278 if os.path.exists(os.path.join(target_files_dir, subdir)):
1279 partition_map[partition] = subdir
1280 break
1281 return partition_map
1282
1283
Daniel Normand3351562020-10-29 12:33:11 -07001284def SharedUidPartitionViolations(uid_dict, partition_groups):
1285 """Checks for APK sharedUserIds that cross partition group boundaries.
1286
1287 This uses a single or merged build's shareduid_violation_modules.json
1288 output file, as generated by find_shareduid_violation.py or
1289 core/tasks/find-shareduid-violation.mk.
1290
1291 An error is defined as a sharedUserId that is found in a set of partitions
1292 that span more than one partition group.
1293
1294 Args:
1295 uid_dict: A dictionary created by using the standard json module to read a
1296 complete shareduid_violation_modules.json file.
1297 partition_groups: A list of groups, where each group is a list of
1298 partitions.
1299
1300 Returns:
1301 A list of error messages.
1302 """
1303 errors = []
1304 for uid, partitions in uid_dict.items():
1305 found_in_groups = [
1306 group for group in partition_groups
1307 if set(partitions.keys()) & set(group)
1308 ]
1309 if len(found_in_groups) > 1:
1310 errors.append(
1311 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1312 % (uid, ",".join(sorted(partitions.keys()))))
1313 return errors
1314
1315
Daniel Norman21c34f72020-11-11 17:25:50 -08001316def RunHostInitVerifier(product_out, partition_map):
1317 """Runs host_init_verifier on the init rc files within partitions.
1318
1319 host_init_verifier searches the etc/init path within each partition.
1320
1321 Args:
1322 product_out: PRODUCT_OUT directory, containing partition directories.
1323 partition_map: A map of partition name -> relative path within product_out.
1324 """
1325 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1326 cmd = ["host_init_verifier"]
1327 for partition, path in partition_map.items():
1328 if partition not in allowed_partitions:
1329 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1330 partition)
1331 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1332 # Add --property-contexts if the file exists on the partition.
1333 property_contexts = "%s_property_contexts" % (
1334 "plat" if partition == "system" else partition)
1335 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1336 property_contexts)
1337 if os.path.exists(property_contexts_path):
1338 cmd.append("--property-contexts=%s" % property_contexts_path)
1339 # Add the passwd file if the file exists on the partition.
1340 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1341 if os.path.exists(passwd_path):
1342 cmd.extend(["-p", passwd_path])
1343 return RunAndCheckOutput(cmd)
1344
1345
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001346def AppendAVBSigningArgs(cmd, partition):
1347 """Append signing arguments for avbtool."""
1348 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1349 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001350 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1351 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1352 if os.path.exists(new_key_path):
1353 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001354 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1355 if key_path and algorithm:
1356 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001357 avb_salt = OPTIONS.info_dict.get("avb_salt")
1358 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001359 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001360 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001361
1362
Tao Bao765668f2019-10-04 22:03:00 -07001363def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001364 """Returns the VBMeta arguments for partition.
1365
1366 It sets up the VBMeta argument by including the partition descriptor from the
1367 given 'image', or by configuring the partition as a chained partition.
1368
1369 Args:
1370 partition: The name of the partition (e.g. "system").
1371 image: The path to the partition image.
1372 info_dict: A dict returned by common.LoadInfoDict(). Will use
1373 OPTIONS.info_dict if None has been given.
1374
1375 Returns:
1376 A list of VBMeta arguments.
1377 """
1378 if info_dict is None:
1379 info_dict = OPTIONS.info_dict
1380
1381 # Check if chain partition is used.
1382 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001383 if not key_path:
1384 return ["--include_descriptors_from_image", image]
1385
1386 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1387 # into vbmeta.img. The recovery image will be configured on an independent
1388 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1389 # See details at
1390 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001391 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001392 return []
1393
1394 # Otherwise chain the partition into vbmeta.
1395 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1396 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001397
1398
Tao Bao02a08592018-07-22 12:40:45 -07001399def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1400 """Constructs and returns the arg to build or verify a chained partition.
1401
1402 Args:
1403 partition: The partition name.
1404 info_dict: The info dict to look up the key info and rollback index
1405 location.
1406 key: The key to be used for building or verifying the partition. Defaults to
1407 the key listed in info_dict.
1408
1409 Returns:
1410 A string of form "partition:rollback_index_location:key" that can be used to
1411 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001412 """
1413 if key is None:
1414 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001415 if key and not os.path.exists(key) and OPTIONS.search_path:
1416 new_key_path = os.path.join(OPTIONS.search_path, key)
1417 if os.path.exists(new_key_path):
1418 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001419 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001420 rollback_index_location = info_dict[
1421 "avb_" + partition + "_rollback_index_location"]
1422 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1423
1424
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001425def _HasGkiCertificationArgs():
1426 return ("gki_signing_key_path" in OPTIONS.info_dict and
1427 "gki_signing_algorithm" in OPTIONS.info_dict)
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001428
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001429
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001430def _GenerateGkiCertificate(image, image_name):
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001431 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001432 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001433
1434 if not os.path.exists(key_path) and OPTIONS.search_path:
1435 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1436 if os.path.exists(new_key_path):
1437 key_path = new_key_path
1438
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001439 # Checks key_path exists, before processing --gki_signing_* args.
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001440 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001441 raise ExternalError(
1442 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001443
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001444 output_certificate = tempfile.NamedTemporaryFile()
1445 cmd = [
1446 "generate_gki_certificate",
1447 "--name", image_name,
1448 "--algorithm", algorithm,
1449 "--key", key_path,
1450 "--output", output_certificate.name,
1451 image,
1452 ]
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001453
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001454 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args", "")
1455 signature_args = signature_args.strip()
1456 if signature_args:
1457 cmd.extend(["--additional_avb_args", signature_args])
1458
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001459 args = OPTIONS.info_dict.get("avb_boot_add_hash_footer_args", "")
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001460 args = args.strip()
1461 if args:
1462 cmd.extend(["--additional_avb_args", args])
1463
1464 RunAndCheckOutput(cmd)
1465
1466 output_certificate.seek(os.SEEK_SET, 0)
1467 data = output_certificate.read()
1468 output_certificate.close()
1469 return data
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001470
1471
Daniel Norman276f0622019-07-26 14:13:51 -07001472def BuildVBMeta(image_path, partitions, name, needed_partitions):
1473 """Creates a VBMeta image.
1474
1475 It generates the requested VBMeta image. The requested image could be for
1476 top-level or chained VBMeta image, which is determined based on the name.
1477
1478 Args:
1479 image_path: The output path for the new VBMeta image.
1480 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001481 values. Only valid partition names are accepted, as partitions listed
1482 in common.AVB_PARTITIONS and custom partitions listed in
1483 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001484 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1485 needed_partitions: Partitions whose descriptors should be included into the
1486 generated VBMeta image.
1487
1488 Raises:
1489 AssertionError: On invalid input args.
1490 """
1491 avbtool = OPTIONS.info_dict["avb_avbtool"]
1492 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1493 AppendAVBSigningArgs(cmd, name)
1494
Hongguang Chenf23364d2020-04-27 18:36:36 -07001495 custom_partitions = OPTIONS.info_dict.get(
1496 "avb_custom_images_partition_list", "").strip().split()
1497
Daniel Norman276f0622019-07-26 14:13:51 -07001498 for partition, path in partitions.items():
1499 if partition not in needed_partitions:
1500 continue
1501 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001502 partition in AVB_VBMETA_PARTITIONS or
1503 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001504 'Unknown partition: {}'.format(partition)
1505 assert os.path.exists(path), \
1506 'Failed to find {} for {}'.format(path, partition)
1507 cmd.extend(GetAvbPartitionArg(partition, path))
1508
1509 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1510 if args and args.strip():
1511 split_args = shlex.split(args)
1512 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001513 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001514 # as a path relative to source tree, which may not be available at the
1515 # same location when running this script (we have the input target_files
1516 # zip only). For such cases, we additionally scan other locations (e.g.
1517 # IMAGES/, RADIO/, etc) before bailing out.
1518 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001519 chained_image = split_args[index + 1]
1520 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001521 continue
1522 found = False
1523 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1524 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001525 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001526 if os.path.exists(alt_path):
1527 split_args[index + 1] = alt_path
1528 found = True
1529 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001530 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001531 cmd.extend(split_args)
1532
1533 RunAndCheckOutput(cmd)
1534
1535
jiajia tang836f76b2021-04-02 14:48:26 +08001536def _MakeRamdisk(sourcedir, fs_config_file=None,
1537 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001538 ramdisk_img = tempfile.NamedTemporaryFile()
1539
1540 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1541 cmd = ["mkbootfs", "-f", fs_config_file,
1542 os.path.join(sourcedir, "RAMDISK")]
1543 else:
1544 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1545 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001546 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001547 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001548 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001549 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001550 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001551 else:
1552 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001553
1554 p2.wait()
1555 p1.wait()
1556 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001557 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001558
1559 return ramdisk_img
1560
1561
Steve Muckle9793cf62020-04-08 18:27:00 -07001562def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001563 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001564 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001565
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001566 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001567 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1568 we are building a two-step special image (i.e. building a recovery image to
1569 be loaded into /boot in two-step OTAs).
1570
1571 Return the image data, or None if sourcedir does not appear to contains files
1572 for building the requested image.
1573 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001574
Yifan Hong63c5ca12020-10-08 11:54:02 -07001575 if info_dict is None:
1576 info_dict = OPTIONS.info_dict
1577
Steve Muckle9793cf62020-04-08 18:27:00 -07001578 # "boot" or "recovery", without extension.
1579 partition_name = os.path.basename(sourcedir).lower()
1580
Yifan Hong63c5ca12020-10-08 11:54:02 -07001581 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001582 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001583 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1584 logger.info("Excluded kernel binary from recovery image.")
1585 else:
1586 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001587 elif partition_name == "init_boot":
1588 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001589 else:
1590 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001591 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001592 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001593 return None
1594
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001595 kernel_path = os.path.join(sourcedir, kernel) if kernel else None
1596
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001597 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001598 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001599
Doug Zongkereef39442009-04-02 12:14:19 -07001600 img = tempfile.NamedTemporaryFile()
1601
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001602 if has_ramdisk:
TJ Rhoades6f488e92022-05-01 22:16:22 -07001603 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001604 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1605 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001606
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001607 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1608 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1609
Yifan Hong63c5ca12020-10-08 11:54:02 -07001610 cmd = [mkbootimg]
Yi-Yo Chiang36054e22022-01-08 22:29:30 +08001611 if kernel_path is not None:
1612 cmd.extend(["--kernel", kernel_path])
Doug Zongker38a649f2009-06-17 09:07:09 -07001613
Benoit Fradina45a8682014-07-14 21:00:43 +02001614 fn = os.path.join(sourcedir, "second")
1615 if os.access(fn, os.F_OK):
1616 cmd.append("--second")
1617 cmd.append(fn)
1618
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001619 fn = os.path.join(sourcedir, "dtb")
1620 if os.access(fn, os.F_OK):
1621 cmd.append("--dtb")
1622 cmd.append(fn)
1623
Doug Zongker171f1cd2009-06-15 22:36:37 -07001624 fn = os.path.join(sourcedir, "cmdline")
1625 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001626 cmd.append("--cmdline")
1627 cmd.append(open(fn).read().rstrip("\n"))
1628
1629 fn = os.path.join(sourcedir, "base")
1630 if os.access(fn, os.F_OK):
1631 cmd.append("--base")
1632 cmd.append(open(fn).read().rstrip("\n"))
1633
Ying Wang4de6b5b2010-08-25 14:29:34 -07001634 fn = os.path.join(sourcedir, "pagesize")
1635 if os.access(fn, os.F_OK):
1636 cmd.append("--pagesize")
1637 cmd.append(open(fn).read().rstrip("\n"))
1638
Steve Mucklef84668e2020-03-16 19:13:46 -07001639 if partition_name == "recovery":
1640 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301641 if not args:
1642 # Fall back to "mkbootimg_args" for recovery image
1643 # in case "recovery_mkbootimg_args" is not set.
1644 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001645 elif partition_name == "init_boot":
1646 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001647 else:
1648 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001649 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001650 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001651
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001652 args = info_dict.get("mkbootimg_version_args")
1653 if args and args.strip():
1654 cmd.extend(shlex.split(args))
Sami Tolvanen3303d902016-03-15 16:49:30 +00001655
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001656 if has_ramdisk:
1657 cmd.extend(["--ramdisk", ramdisk_img.name])
1658
Tao Baod95e9fd2015-03-29 23:07:41 -07001659 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001660 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001661 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001662 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001663 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001664 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001665
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001666 if partition_name == "recovery":
1667 if info_dict.get("include_recovery_dtbo") == "true":
1668 fn = os.path.join(sourcedir, "recovery_dtbo")
1669 cmd.extend(["--recovery_dtbo", fn])
1670 if info_dict.get("include_recovery_acpio") == "true":
1671 fn = os.path.join(sourcedir, "recovery_acpio")
1672 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001673
Tao Bao986ee862018-10-04 15:46:16 -07001674 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001675
Yi-Yo Chiang24da1a42022-02-22 19:51:15 +08001676 if _HasGkiCertificationArgs():
1677 if not os.path.exists(img.name):
1678 raise ValueError("Cannot find GKI boot.img")
1679 if kernel_path is None or not os.path.exists(kernel_path):
1680 raise ValueError("Cannot find GKI kernel.img")
1681
1682 # Certify GKI images.
1683 boot_signature_bytes = b''
1684 boot_signature_bytes += _GenerateGkiCertificate(img.name, "boot")
1685 boot_signature_bytes += _GenerateGkiCertificate(
1686 kernel_path, "generic_kernel")
1687
1688 BOOT_SIGNATURE_SIZE = 16 * 1024
1689 if len(boot_signature_bytes) > BOOT_SIGNATURE_SIZE:
1690 raise ValueError(
1691 f"GKI boot_signature size must be <= {BOOT_SIGNATURE_SIZE}")
1692 boot_signature_bytes += (
1693 b'\0' * (BOOT_SIGNATURE_SIZE - len(boot_signature_bytes)))
1694 assert len(boot_signature_bytes) == BOOT_SIGNATURE_SIZE
1695
1696 with open(img.name, 'ab') as f:
1697 f.write(boot_signature_bytes)
1698
Tao Baod95e9fd2015-03-29 23:07:41 -07001699 # Sign the image if vboot is non-empty.
hungweichen22e3b012022-08-19 06:35:43 +00001700 if info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001701 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001702 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001703 # We have switched from the prebuilt futility binary to using the tool
1704 # (futility-host) built from the source. Override the setting in the old
1705 # TF.zip.
1706 futility = info_dict["futility"]
1707 if futility.startswith("prebuilts/"):
1708 futility = "futility-host"
1709 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001710 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001711 info_dict["vboot_key"] + ".vbprivk",
1712 info_dict["vboot_subkey"] + ".vbprivk",
1713 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001714 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001715 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001716
Tao Baof3282b42015-04-01 11:21:55 -07001717 # Clean up the temp files.
1718 img_unsigned.close()
1719 img_keyblock.close()
1720
David Zeuthen8fecb282017-12-01 16:24:01 -05001721 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001722 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001723 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001724 if partition_name == "recovery":
1725 part_size = info_dict["recovery_size"]
1726 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001727 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001728 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001729 "--partition_size", str(part_size), "--partition_name",
1730 partition_name]
1731 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001732 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001733 if args and args.strip():
1734 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001735 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001736
1737 img.seek(os.SEEK_SET, 0)
1738 data = img.read()
1739
1740 if has_ramdisk:
1741 ramdisk_img.close()
1742 img.close()
1743
1744 return data
1745
1746
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001747def _SignBootableImage(image_path, prebuilt_name, partition_name,
1748 info_dict=None):
1749 """Performs AVB signing for a prebuilt boot.img.
1750
1751 Args:
1752 image_path: The full path of the image, e.g., /path/to/boot.img.
1753 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001754 boot-5.10.img, recovery.img or init_boot.img.
1755 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001756 info_dict: The information dict read from misc_info.txt.
1757 """
1758 if info_dict is None:
1759 info_dict = OPTIONS.info_dict
1760
1761 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1762 if info_dict.get("avb_enable") == "true":
1763 avbtool = info_dict["avb_avbtool"]
1764 if partition_name == "recovery":
1765 part_size = info_dict["recovery_size"]
1766 else:
1767 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1768
1769 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1770 "--partition_size", str(part_size), "--partition_name",
1771 partition_name]
1772 AppendAVBSigningArgs(cmd, partition_name)
1773 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1774 if args and args.strip():
1775 cmd.extend(shlex.split(args))
1776 RunAndCheckOutput(cmd)
1777
1778
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001779def HasRamdisk(partition_name, info_dict=None):
1780 """Returns true/false to see if a bootable image should have a ramdisk.
1781
1782 Args:
1783 partition_name: The partition name, e.g., 'boot', 'init_boot' or 'recovery'.
1784 info_dict: The information dict read from misc_info.txt.
1785 """
1786 if info_dict is None:
1787 info_dict = OPTIONS.info_dict
1788
1789 if partition_name != "boot":
1790 return True # init_boot.img or recovery.img has a ramdisk.
1791
1792 if info_dict.get("recovery_as_boot") == "true":
1793 return True # the recovery-as-boot boot.img has a RECOVERY ramdisk.
1794
Bowgo Tsai85578e02022-04-19 10:50:59 +08001795 if info_dict.get("gki_boot_image_without_ramdisk") == "true":
1796 return False # A GKI boot.img has no ramdisk since Android-13.
1797
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001798 if info_dict.get("system_root_image") == "true":
1799 # The ramdisk content is merged into the system.img, so there is NO
1800 # ramdisk in the boot.img or boot-<kernel version>.img.
1801 return False
1802
1803 if info_dict.get("init_boot") == "true":
1804 # The ramdisk is moved to the init_boot.img, so there is NO
1805 # ramdisk in the boot.img or boot-<kernel version>.img.
1806 return False
1807
1808 return True
1809
1810
Doug Zongkerd5131602012-08-02 14:46:42 -07001811def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001812 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001813 """Return a File object with the desired bootable image.
1814
1815 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1816 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1817 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001818
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001819 if info_dict is None:
1820 info_dict = OPTIONS.info_dict
1821
Doug Zongker55d93282011-01-25 17:03:34 -08001822 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1823 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001824 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001825 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001826
1827 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1828 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001829 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001830 return File.FromLocalFile(name, prebuilt_path)
1831
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001832 partition_name = tree_subdir.lower()
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001833 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1834 if os.path.exists(prebuilt_path):
1835 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1836 signed_img = MakeTempFile()
1837 shutil.copy(prebuilt_path, signed_img)
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001838 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1839 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001840
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001841 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001842
Bowgo Tsai88fc2bd2022-01-05 20:19:25 +08001843 has_ramdisk = HasRamdisk(partition_name, info_dict)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001844
Doug Zongker6f1d0312014-08-22 08:07:12 -07001845 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001846 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001847 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001848 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001849 if data:
1850 return File(name, data)
1851 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001852
Doug Zongkereef39442009-04-02 12:14:19 -07001853
Lucas Wei03230252022-04-18 16:00:40 +08001854def _BuildVendorBootImage(sourcedir, partition_name, info_dict=None):
Steve Mucklee1b10862019-07-10 10:49:37 -07001855 """Build a vendor boot image from the specified sourcedir.
1856
1857 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1858 turn them into a vendor boot image.
1859
1860 Return the image data, or None if sourcedir does not appear to contains files
1861 for building the requested image.
1862 """
1863
1864 if info_dict is None:
1865 info_dict = OPTIONS.info_dict
1866
1867 img = tempfile.NamedTemporaryFile()
1868
TJ Rhoades6f488e92022-05-01 22:16:22 -07001869 ramdisk_format = GetRamdiskFormat(info_dict)
jiajia tang836f76b2021-04-02 14:48:26 +08001870 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001871
1872 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1873 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1874
1875 cmd = [mkbootimg]
1876
1877 fn = os.path.join(sourcedir, "dtb")
1878 if os.access(fn, os.F_OK):
Kelvin Zhangf294c872022-10-06 14:21:36 -07001879 has_vendor_kernel_boot = (info_dict.get(
1880 "vendor_kernel_boot", "").lower() == "true")
Lucas Wei03230252022-04-18 16:00:40 +08001881
1882 # Pack dtb into vendor_kernel_boot if building vendor_kernel_boot.
1883 # Otherwise pack dtb into vendor_boot.
1884 if not has_vendor_kernel_boot or partition_name == "vendor_kernel_boot":
1885 cmd.append("--dtb")
1886 cmd.append(fn)
Steve Mucklee1b10862019-07-10 10:49:37 -07001887
1888 fn = os.path.join(sourcedir, "vendor_cmdline")
1889 if os.access(fn, os.F_OK):
1890 cmd.append("--vendor_cmdline")
1891 cmd.append(open(fn).read().rstrip("\n"))
1892
1893 fn = os.path.join(sourcedir, "base")
1894 if os.access(fn, os.F_OK):
1895 cmd.append("--base")
1896 cmd.append(open(fn).read().rstrip("\n"))
1897
1898 fn = os.path.join(sourcedir, "pagesize")
1899 if os.access(fn, os.F_OK):
1900 cmd.append("--pagesize")
1901 cmd.append(open(fn).read().rstrip("\n"))
1902
1903 args = info_dict.get("mkbootimg_args")
1904 if args and args.strip():
1905 cmd.extend(shlex.split(args))
1906
1907 args = info_dict.get("mkbootimg_version_args")
1908 if args and args.strip():
1909 cmd.extend(shlex.split(args))
1910
1911 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1912 cmd.extend(["--vendor_boot", img.name])
1913
Devin Moore50509012021-01-13 10:45:04 -08001914 fn = os.path.join(sourcedir, "vendor_bootconfig")
1915 if os.access(fn, os.F_OK):
1916 cmd.append("--vendor_bootconfig")
1917 cmd.append(fn)
1918
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001919 ramdisk_fragment_imgs = []
1920 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1921 if os.access(fn, os.F_OK):
1922 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1923 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001924 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1925 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001926 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001927 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1928 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001929 # Use prebuilt image if found, else create ramdisk from supplied files.
1930 if os.access(fn, os.F_OK):
1931 ramdisk_fragment_pathname = fn
1932 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001933 ramdisk_fragment_root = os.path.join(
1934 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001935 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1936 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001937 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1938 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1939 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1940
Steve Mucklee1b10862019-07-10 10:49:37 -07001941 RunAndCheckOutput(cmd)
1942
1943 # AVB: if enabled, calculate and add hash.
1944 if info_dict.get("avb_enable") == "true":
1945 avbtool = info_dict["avb_avbtool"]
Lucas Wei03230252022-04-18 16:00:40 +08001946 part_size = info_dict[f'{partition_name}_size']
Steve Mucklee1b10862019-07-10 10:49:37 -07001947 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Lucas Wei03230252022-04-18 16:00:40 +08001948 "--partition_size", str(part_size), "--partition_name", partition_name]
1949 AppendAVBSigningArgs(cmd, partition_name)
1950 args = info_dict.get(f'avb_{partition_name}_add_hash_footer_args')
Steve Mucklee1b10862019-07-10 10:49:37 -07001951 if args and args.strip():
1952 cmd.extend(shlex.split(args))
1953 RunAndCheckOutput(cmd)
1954
1955 img.seek(os.SEEK_SET, 0)
1956 data = img.read()
1957
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001958 for f in ramdisk_fragment_imgs:
1959 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001960 ramdisk_img.close()
1961 img.close()
1962
1963 return data
1964
1965
1966def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1967 info_dict=None):
1968 """Return a File object with the desired vendor boot image.
1969
1970 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1971 the source files in 'unpack_dir'/'tree_subdir'."""
1972
1973 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1974 if os.path.exists(prebuilt_path):
1975 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1976 return File.FromLocalFile(name, prebuilt_path)
1977
1978 logger.info("building image from target_files %s...", tree_subdir)
1979
1980 if info_dict is None:
1981 info_dict = OPTIONS.info_dict
1982
Kelvin Zhang0876c412020-06-23 15:06:58 -04001983 data = _BuildVendorBootImage(
Lucas Wei03230252022-04-18 16:00:40 +08001984 os.path.join(unpack_dir, tree_subdir), "vendor_boot", info_dict)
1985 if data:
1986 return File(name, data)
1987 return None
1988
1989
1990def GetVendorKernelBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
Kelvin Zhangf294c872022-10-06 14:21:36 -07001991 info_dict=None):
Lucas Wei03230252022-04-18 16:00:40 +08001992 """Return a File object with the desired vendor kernel boot image.
1993
1994 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1995 the source files in 'unpack_dir'/'tree_subdir'."""
1996
1997 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1998 if os.path.exists(prebuilt_path):
1999 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
2000 return File.FromLocalFile(name, prebuilt_path)
2001
2002 logger.info("building image from target_files %s...", tree_subdir)
2003
2004 if info_dict is None:
2005 info_dict = OPTIONS.info_dict
2006
2007 data = _BuildVendorBootImage(
2008 os.path.join(unpack_dir, tree_subdir), "vendor_kernel_boot", info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07002009 if data:
2010 return File(name, data)
2011 return None
2012
2013
Narayan Kamatha07bf042017-08-14 14:49:21 +01002014def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08002015 """Gunzips the given gzip compressed file to a given output file."""
2016 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002017 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01002018 shutil.copyfileobj(in_file, out_file)
2019
2020
Tao Bao0ff15de2019-03-20 11:26:06 -07002021def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002022 """Unzips the archive to the given directory.
2023
2024 Args:
2025 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002026 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07002027 patterns: Files to unzip from the archive. If omitted, will unzip the entire
2028 archvie. Non-matching patterns will be filtered out. If there's no match
2029 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002030 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002031 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07002032 if patterns is not None:
2033 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04002034 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07002035 names = input_zip.namelist()
2036 filtered = [
2037 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
2038
2039 # There isn't any matching files. Don't unzip anything.
2040 if not filtered:
2041 return
2042 cmd.extend(filtered)
2043
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002044 RunAndCheckOutput(cmd)
2045
2046
Daniel Norman78554ea2021-09-14 10:29:38 -07002047def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08002048 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08002049
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002050 Args:
2051 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
2052 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
2053
Daniel Norman78554ea2021-09-14 10:29:38 -07002054 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08002055 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08002056
Tao Bao1c830bf2017-12-25 10:43:47 -08002057 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08002058 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08002059 """
Doug Zongkereef39442009-04-02 12:14:19 -07002060
Tao Bao1c830bf2017-12-25 10:43:47 -08002061 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08002062 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
2063 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07002064 UnzipToDir(m.group(1), tmp, patterns)
2065 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002066 filename = m.group(1)
2067 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07002068 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08002069
Tao Baodba59ee2018-01-09 13:21:02 -08002070 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07002071
2072
Yifan Hong8a66a712019-04-04 15:37:57 -07002073def GetUserImage(which, tmpdir, input_zip,
2074 info_dict=None,
2075 allow_shared_blocks=None,
Yifan Hong8a66a712019-04-04 15:37:57 -07002076 reset_file_map=False):
2077 """Returns an Image object suitable for passing to BlockImageDiff.
2078
2079 This function loads the specified image from the given path. If the specified
2080 image is sparse, it also performs additional processing for OTA purpose. For
2081 example, it always adds block 0 to clobbered blocks list. It also detects
2082 files that cannot be reconstructed from the block list, for whom we should
2083 avoid applying imgdiff.
2084
2085 Args:
2086 which: The partition name.
2087 tmpdir: The directory that contains the prebuilt image and block map file.
2088 input_zip: The target-files ZIP archive.
2089 info_dict: The dict to be looked up for relevant info.
2090 allow_shared_blocks: If image is sparse, whether having shared blocks is
2091 allowed. If none, it is looked up from info_dict.
Yifan Hong8a66a712019-04-04 15:37:57 -07002092 reset_file_map: If true and image is sparse, reset file map before returning
2093 the image.
2094 Returns:
2095 A Image object. If it is a sparse image and reset_file_map is False, the
2096 image will have file_map info loaded.
2097 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002098 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002099 info_dict = LoadInfoDict(input_zip)
2100
2101 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07002102 if info_dict.get(which + "_disable_sparse"):
2103 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002104
2105 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2106 # shared blocks (i.e. some blocks will show up in multiple files' block
2107 # list). We can only allocate such shared blocks to the first "owner", and
2108 # disable imgdiff for all later occurrences.
2109 if allow_shared_blocks is None:
2110 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2111
2112 if is_sparse:
hungweichencc9c05d2022-08-23 05:45:42 +00002113 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks)
Yifan Hong8a66a712019-04-04 15:37:57 -07002114 if reset_file_map:
2115 img.ResetFileMap()
2116 return img
hungweichencc9c05d2022-08-23 05:45:42 +00002117 return GetNonSparseImage(which, tmpdir)
Yifan Hong8a66a712019-04-04 15:37:57 -07002118
2119
hungweichencc9c05d2022-08-23 05:45:42 +00002120def GetNonSparseImage(which, tmpdir):
Yifan Hong8a66a712019-04-04 15:37:57 -07002121 """Returns a Image object suitable for passing to BlockImageDiff.
2122
2123 This function loads the specified non-sparse image from the given path.
2124
2125 Args:
2126 which: The partition name.
2127 tmpdir: The directory that contains the prebuilt image and block map file.
2128 Returns:
2129 A Image object.
2130 """
2131 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2132 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2133
2134 # The image and map files must have been created prior to calling
2135 # ota_from_target_files.py (since LMP).
2136 assert os.path.exists(path) and os.path.exists(mappath)
2137
hungweichencc9c05d2022-08-23 05:45:42 +00002138 return images.FileImage(path)
Tianjie Xu41976c72019-07-03 13:57:01 -07002139
Yifan Hong8a66a712019-04-04 15:37:57 -07002140
hungweichencc9c05d2022-08-23 05:45:42 +00002141def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks):
Tao Baoc765cca2018-01-31 17:32:40 -08002142 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2143
2144 This function loads the specified sparse image from the given path, and
2145 performs additional processing for OTA purpose. For example, it always adds
2146 block 0 to clobbered blocks list. It also detects files that cannot be
2147 reconstructed from the block list, for whom we should avoid applying imgdiff.
2148
2149 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002150 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002151 tmpdir: The directory that contains the prebuilt image and block map file.
2152 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002153 allow_shared_blocks: Whether having shared blocks is allowed.
Tao Baoc765cca2018-01-31 17:32:40 -08002154 Returns:
2155 A SparseImage object, with file_map info loaded.
2156 """
Tao Baoc765cca2018-01-31 17:32:40 -08002157 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2158 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2159
2160 # The image and map files must have been created prior to calling
2161 # ota_from_target_files.py (since LMP).
2162 assert os.path.exists(path) and os.path.exists(mappath)
2163
2164 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2165 # it to clobbered_blocks so that it will be written to the target
2166 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2167 clobbered_blocks = "0"
2168
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002169 image = sparse_img.SparseImage(
hungweichencc9c05d2022-08-23 05:45:42 +00002170 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks)
Tao Baoc765cca2018-01-31 17:32:40 -08002171
2172 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2173 # if they contain all zeros. We can't reconstruct such a file from its block
2174 # list. Tag such entries accordingly. (Bug: 65213616)
2175 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002176 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002177 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002178 continue
2179
Tom Cherryd14b8952018-08-09 14:26:00 -07002180 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2181 # filename listed in system.map may contain an additional leading slash
2182 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2183 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002184 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002185 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002186 arcname = entry.lstrip('/')
2187 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002188 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002189 else:
2190 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002191
2192 assert arcname in input_zip.namelist(), \
2193 "Failed to find the ZIP entry for {}".format(entry)
2194
Tao Baoc765cca2018-01-31 17:32:40 -08002195 info = input_zip.getinfo(arcname)
2196 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002197
2198 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002199 # image, check the original block list to determine its completeness. Note
2200 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002201 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002202 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002203
Tao Baoc765cca2018-01-31 17:32:40 -08002204 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2205 ranges.extra['incomplete'] = True
2206
2207 return image
2208
2209
Doug Zongkereef39442009-04-02 12:14:19 -07002210def GetKeyPasswords(keylist):
2211 """Given a list of keys, prompt the user to enter passwords for
2212 those which require them. Return a {key: password} dict. password
2213 will be None if the key has no password."""
2214
Doug Zongker8ce7c252009-05-22 13:34:54 -07002215 no_passwords = []
2216 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002217 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002218 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002219
2220 # sorted() can't compare strings to None, so convert Nones to strings
2221 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002222 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002223 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002224 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002225 continue
2226
T.R. Fullhart37e10522013-03-18 10:31:26 -07002227 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002228 "-inform", "DER", "-nocrypt"],
2229 stdin=devnull.fileno(),
2230 stdout=devnull.fileno(),
2231 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002232 p.communicate()
2233 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002234 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002235 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002236 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002237 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2238 "-inform", "DER", "-passin", "pass:"],
2239 stdin=devnull.fileno(),
2240 stdout=devnull.fileno(),
2241 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002242 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002243 if p.returncode == 0:
2244 # Encrypted key with empty string as password.
2245 key_passwords[k] = ''
2246 elif stderr.startswith('Error decrypting key'):
2247 # Definitely encrypted key.
2248 # It would have said "Error reading key" if it didn't parse correctly.
2249 need_passwords.append(k)
2250 else:
2251 # Potentially, a type of key that openssl doesn't understand.
2252 # We'll let the routines in signapk.jar handle it.
2253 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002254 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255
T.R. Fullhart37e10522013-03-18 10:31:26 -07002256 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002257 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002258 return key_passwords
2259
2260
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002261def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002262 """Gets the minSdkVersion declared in the APK.
2263
Martin Stjernholm58472e82022-01-07 22:08:47 +00002264 It calls OPTIONS.aapt2_path to query the embedded minSdkVersion from the given
2265 APK file. This can be both a decimal number (API Level) or a codename.
Tao Baof47bf0f2018-03-21 23:28:51 -07002266
2267 Args:
2268 apk_name: The APK filename.
2269
2270 Returns:
2271 The parsed SDK version string.
2272
2273 Raises:
2274 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002275 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002276 proc = Run(
Martin Stjernholm58472e82022-01-07 22:08:47 +00002277 [OPTIONS.aapt2_path, "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002278 stderr=subprocess.PIPE)
2279 stdoutdata, stderrdata = proc.communicate()
2280 if proc.returncode != 0:
2281 raise ExternalError(
Kelvin Zhang21118bb2022-02-12 09:40:35 -08002282 "Failed to obtain minSdkVersion for {}: aapt2 return code {}:\n{}\n{}".format(
2283 apk_name, proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002284
Tao Baof47bf0f2018-03-21 23:28:51 -07002285 for line in stdoutdata.split("\n"):
2286 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002287 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2288 if m:
2289 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002290 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002291
2292
2293def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002294 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002295
Tao Baof47bf0f2018-03-21 23:28:51 -07002296 If minSdkVersion is set to a codename, it is translated to a number using the
2297 provided map.
2298
2299 Args:
2300 apk_name: The APK filename.
2301
2302 Returns:
2303 The parsed SDK version number.
2304
2305 Raises:
2306 ExternalError: On failing to get the min SDK version number.
2307 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002308 version = GetMinSdkVersion(apk_name)
2309 try:
2310 return int(version)
2311 except ValueError:
2312 # Not a decimal number. Codename?
2313 if version in codename_to_api_level_map:
2314 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002315 raise ExternalError(
2316 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2317 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002318
2319
2320def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002321 codename_to_api_level_map=None, whole_file=False,
2322 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002323 """Sign the input_name zip/jar/apk, producing output_name. Use the
2324 given key and password (the latter may be None if the key does not
2325 have a password.
2326
Doug Zongker951495f2009-08-14 12:44:19 -07002327 If whole_file is true, use the "-w" option to SignApk to embed a
2328 signature that covers the whole file in the archive comment of the
2329 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002330
2331 min_api_level is the API Level (int) of the oldest platform this file may end
2332 up on. If not specified for an APK, the API Level is obtained by interpreting
2333 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2334
2335 codename_to_api_level_map is needed to translate the codename which may be
2336 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002337
2338 Caller may optionally specify extra args to be passed to SignApk, which
2339 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002340 """
Tao Bao76def242017-11-21 09:25:31 -08002341 if codename_to_api_level_map is None:
2342 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002343 if extra_signapk_args is None:
2344 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002345
Alex Klyubin9667b182015-12-10 13:38:50 -08002346 java_library_path = os.path.join(
2347 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2348
Tao Baoe95540e2016-11-08 12:08:53 -08002349 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2350 ["-Djava.library.path=" + java_library_path,
2351 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002352 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002353 if whole_file:
2354 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002355
2356 min_sdk_version = min_api_level
2357 if min_sdk_version is None:
2358 if not whole_file:
2359 min_sdk_version = GetMinSdkVersionInt(
2360 input_name, codename_to_api_level_map)
2361 if min_sdk_version is not None:
2362 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2363
T.R. Fullhart37e10522013-03-18 10:31:26 -07002364 cmd.extend([key + OPTIONS.public_key_suffix,
2365 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002366 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002367
Tao Bao73dd4f42018-10-04 16:25:33 -07002368 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002369 if password is not None:
2370 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002371 stdoutdata, _ = proc.communicate(password)
2372 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002373 raise ExternalError(
Kelvin Zhang197772f2022-04-26 15:15:11 -07002374 "Failed to run {}: return code {}:\n{}".format(cmd,
Kelvin Zhangf294c872022-10-06 14:21:36 -07002375 proc.returncode, stdoutdata))
2376
Doug Zongkereef39442009-04-02 12:14:19 -07002377
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002378def SignSePolicy(sepolicy, key, password):
2379 """Sign the sepolicy zip, producing an fsverity .fsv_sig and
2380 an RSA .sig signature files.
2381 """
2382
2383 if OPTIONS.sign_sepolicy_path is None:
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002384 logger.info("No sign_sepolicy_path specified, %s was not signed", sepolicy)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002385 return False
2386
2387 java_library_path = os.path.join(
2388 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2389
2390 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002391 ["-Djava.library.path=" + java_library_path,
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002392 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.sign_sepolicy_path)] +
Kelvin Zhangf294c872022-10-06 14:21:36 -07002393 OPTIONS.extra_sign_sepolicy_args)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002394
2395 cmd.extend([key + OPTIONS.public_key_suffix,
2396 key + OPTIONS.private_key_suffix,
Melisa Carranza Zuniga7ef13792022-08-23 19:09:12 +02002397 sepolicy, os.path.dirname(sepolicy)])
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002398
2399 proc = Run(cmd, stdin=subprocess.PIPE)
2400 if password is not None:
2401 password += "\n"
2402 stdoutdata, _ = proc.communicate(password)
2403 if proc.returncode != 0:
2404 raise ExternalError(
2405 "Failed to run sign sepolicy: return code {}:\n{}".format(
2406 proc.returncode, stdoutdata))
2407 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002408
Kelvin Zhangf294c872022-10-06 14:21:36 -07002409
Doug Zongker37974732010-09-16 17:44:38 -07002410def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002411 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002412
Tao Bao9dd909e2017-11-14 11:27:32 -08002413 For non-AVB images, raise exception if the data is too big. Print a warning
2414 if the data is nearing the maximum size.
2415
2416 For AVB images, the actual image size should be identical to the limit.
2417
2418 Args:
2419 data: A string that contains all the data for the partition.
2420 target: The partition name. The ".img" suffix is optional.
2421 info_dict: The dict to be looked up for relevant info.
2422 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002423 if target.endswith(".img"):
2424 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002425 mount_point = "/" + target
2426
Ying Wangf8824af2014-06-03 14:07:27 -07002427 fs_type = None
2428 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002429 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002430 if mount_point == "/userdata":
2431 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002432 p = info_dict["fstab"][mount_point]
2433 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002434 device = p.device
2435 if "/" in device:
2436 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002437 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002438 if not fs_type or not limit:
2439 return
Doug Zongkereef39442009-04-02 12:14:19 -07002440
Andrew Boie0f9aec82012-02-14 09:32:52 -08002441 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002442 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2443 # path.
2444 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2445 if size != limit:
2446 raise ExternalError(
2447 "Mismatching image size for %s: expected %d actual %d" % (
2448 target, limit, size))
2449 else:
2450 pct = float(size) * 100.0 / limit
2451 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2452 if pct >= 99.0:
2453 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002454
2455 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002456 logger.warning("\n WARNING: %s\n", msg)
2457 else:
2458 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002459
2460
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002461def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002462 """Parses the APK certs info from a given target-files zip.
2463
2464 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2465 tuple with the following elements: (1) a dictionary that maps packages to
2466 certs (based on the "certificate" and "private_key" attributes in the file;
2467 (2) a string representing the extension of compressed APKs in the target files
2468 (e.g ".gz", ".bro").
2469
2470 Args:
2471 tf_zip: The input target_files ZipFile (already open).
2472
2473 Returns:
2474 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2475 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2476 no compressed APKs.
2477 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002478 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002479 compressed_extension = None
2480
Tao Bao0f990332017-09-08 19:02:54 -07002481 # META/apkcerts.txt contains the info for _all_ the packages known at build
2482 # time. Filter out the ones that are not installed.
2483 installed_files = set()
2484 for name in tf_zip.namelist():
2485 basename = os.path.basename(name)
2486 if basename:
2487 installed_files.add(basename)
2488
Tao Baoda30cfa2017-12-01 16:19:46 -08002489 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002490 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002491 if not line:
2492 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002493 m = re.match(
2494 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002495 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2496 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002497 line)
2498 if not m:
2499 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002500
Tao Bao818ddf52018-01-05 11:17:34 -08002501 matches = m.groupdict()
2502 cert = matches["CERT"]
2503 privkey = matches["PRIVKEY"]
2504 name = matches["NAME"]
2505 this_compressed_extension = matches["COMPRESSED"]
2506
2507 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2508 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2509 if cert in SPECIAL_CERT_STRINGS and not privkey:
2510 certmap[name] = cert
2511 elif (cert.endswith(OPTIONS.public_key_suffix) and
2512 privkey.endswith(OPTIONS.private_key_suffix) and
2513 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2514 certmap[name] = cert[:-public_key_suffix_len]
2515 else:
2516 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2517
2518 if not this_compressed_extension:
2519 continue
2520
2521 # Only count the installed files.
2522 filename = name + '.' + this_compressed_extension
2523 if filename not in installed_files:
2524 continue
2525
2526 # Make sure that all the values in the compression map have the same
2527 # extension. We don't support multiple compression methods in the same
2528 # system image.
2529 if compressed_extension:
2530 if this_compressed_extension != compressed_extension:
2531 raise ValueError(
2532 "Multiple compressed extensions: {} vs {}".format(
2533 compressed_extension, this_compressed_extension))
2534 else:
2535 compressed_extension = this_compressed_extension
2536
2537 return (certmap,
2538 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002539
2540
Doug Zongkereef39442009-04-02 12:14:19 -07002541COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002542Global options
2543
2544 -p (--path) <dir>
2545 Prepend <dir>/bin to the list of places to search for binaries run by this
2546 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002547
Doug Zongker05d3dea2009-06-22 11:32:31 -07002548 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002549 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002550
Tao Bao30df8b42018-04-23 15:32:53 -07002551 -x (--extra) <key=value>
2552 Add a key/value pair to the 'extras' dict, which device-specific extension
2553 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002554
Doug Zongkereef39442009-04-02 12:14:19 -07002555 -v (--verbose)
2556 Show command lines being executed.
2557
2558 -h (--help)
2559 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002560
2561 --logfile <file>
2562 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002563"""
2564
Kelvin Zhang0876c412020-06-23 15:06:58 -04002565
Doug Zongkereef39442009-04-02 12:14:19 -07002566def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002567 print(docstring.rstrip("\n"))
2568 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002569
2570
2571def ParseOptions(argv,
2572 docstring,
2573 extra_opts="", extra_long_opts=(),
2574 extra_option_handler=None):
2575 """Parse the options in argv and return any arguments that aren't
2576 flags. docstring is the calling module's docstring, to be displayed
2577 for errors and -h. extra_opts and extra_long_opts are for flags
2578 defined by the caller, which are processed by passing them to
2579 extra_option_handler."""
2580
2581 try:
2582 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002583 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002584 ["help", "verbose", "path=", "signapk_path=",
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002585 "signapk_shared_library_path=", "extra_signapk_args=",
2586 "sign_sepolicy_path=", "extra_sign_sepolicy_args=", "aapt2_path=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002587 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002588 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2589 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002590 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002591 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002592 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002593 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002594 sys.exit(2)
2595
Doug Zongkereef39442009-04-02 12:14:19 -07002596 for o, a in opts:
2597 if o in ("-h", "--help"):
2598 Usage(docstring)
2599 sys.exit()
2600 elif o in ("-v", "--verbose"):
2601 OPTIONS.verbose = True
2602 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002603 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002604 elif o in ("--signapk_path",):
2605 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002606 elif o in ("--signapk_shared_library_path",):
2607 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002608 elif o in ("--extra_signapk_args",):
2609 OPTIONS.extra_signapk_args = shlex.split(a)
Melisa Carranza Zunigae0a977a2022-06-16 18:44:27 +02002610 elif o in ("--sign_sepolicy_path",):
2611 OPTIONS.sign_sepolicy_path = a
2612 elif o in ("--extra_sign_sepolicy_args",):
2613 OPTIONS.extra_sign_sepolicy_args = shlex.split(a)
Martin Stjernholm58472e82022-01-07 22:08:47 +00002614 elif o in ("--aapt2_path",):
2615 OPTIONS.aapt2_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002616 elif o in ("--java_path",):
2617 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002618 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002619 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002620 elif o in ("--android_jar_path",):
2621 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002622 elif o in ("--public_key_suffix",):
2623 OPTIONS.public_key_suffix = a
2624 elif o in ("--private_key_suffix",):
2625 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002626 elif o in ("--boot_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002627 raise ValueError(
2628 "--boot_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002629 elif o in ("--boot_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002630 raise ValueError(
2631 "--boot_signer_args is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002632 elif o in ("--verity_signer_path",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002633 raise ValueError(
2634 "--verity_signer_path is no longer supported, please switch to AVB")
Baligh Uddin601ddea2015-06-09 15:48:14 -07002635 elif o in ("--verity_signer_args",):
Kelvin Zhangf294c872022-10-06 14:21:36 -07002636 raise ValueError(
2637 "--verity_signer_args is no longer supported, please switch to AVB")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002638 elif o in ("-s", "--device_specific"):
2639 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002640 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002641 key, value = a.split("=", 1)
2642 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002643 elif o in ("--logfile",):
2644 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002645 else:
2646 if extra_option_handler is None or not extra_option_handler(o, a):
2647 assert False, "unknown option \"%s\"" % (o,)
2648
Doug Zongker85448772014-09-09 14:59:20 -07002649 if OPTIONS.search_path:
2650 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2651 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002652
2653 return args
2654
2655
Tao Bao4c851b12016-09-19 13:54:38 -07002656def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002657 """Make a temp file and add it to the list of things to be deleted
2658 when Cleanup() is called. Return the filename."""
2659 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2660 os.close(fd)
2661 OPTIONS.tempfiles.append(fn)
2662 return fn
2663
2664
Tao Bao1c830bf2017-12-25 10:43:47 -08002665def MakeTempDir(prefix='tmp', suffix=''):
2666 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2667
2668 Returns:
2669 The absolute pathname of the new directory.
2670 """
2671 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2672 OPTIONS.tempfiles.append(dir_name)
2673 return dir_name
2674
2675
Doug Zongkereef39442009-04-02 12:14:19 -07002676def Cleanup():
2677 for i in OPTIONS.tempfiles:
2678 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002679 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002680 else:
2681 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002682 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002683
2684
2685class PasswordManager(object):
2686 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002687 self.editor = os.getenv("EDITOR")
2688 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002689
2690 def GetPasswords(self, items):
2691 """Get passwords corresponding to each string in 'items',
2692 returning a dict. (The dict may have keys in addition to the
2693 values in 'items'.)
2694
2695 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2696 user edit that file to add more needed passwords. If no editor is
2697 available, or $ANDROID_PW_FILE isn't define, prompts the user
2698 interactively in the ordinary way.
2699 """
2700
2701 current = self.ReadFile()
2702
2703 first = True
2704 while True:
2705 missing = []
2706 for i in items:
2707 if i not in current or not current[i]:
2708 missing.append(i)
2709 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002710 if not missing:
2711 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002712
2713 for i in missing:
2714 current[i] = ""
2715
2716 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002717 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002718 if sys.version_info[0] >= 3:
2719 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002720 answer = raw_input("try to edit again? [y]> ").strip()
2721 if answer and answer[0] not in 'yY':
2722 raise RuntimeError("key passwords unavailable")
2723 first = False
2724
2725 current = self.UpdateAndReadFile(current)
2726
Kelvin Zhang0876c412020-06-23 15:06:58 -04002727 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002728 """Prompt the user to enter a value (password) for each key in
2729 'current' whose value is fales. Returns a new dict with all the
2730 values.
2731 """
2732 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002733 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002734 if v:
2735 result[k] = v
2736 else:
2737 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002738 result[k] = getpass.getpass(
2739 "Enter password for %s key> " % k).strip()
2740 if result[k]:
2741 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002742 return result
2743
2744 def UpdateAndReadFile(self, current):
2745 if not self.editor or not self.pwfile:
2746 return self.PromptResult(current)
2747
2748 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002749 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002750 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2751 f.write("# (Additional spaces are harmless.)\n\n")
2752
2753 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002754 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002755 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002756 f.write("[[[ %s ]]] %s\n" % (v, k))
2757 if not v and first_line is None:
2758 # position cursor on first line with no password.
2759 first_line = i + 4
2760 f.close()
2761
Tao Bao986ee862018-10-04 15:46:16 -07002762 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002763
2764 return self.ReadFile()
2765
2766 def ReadFile(self):
2767 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002768 if self.pwfile is None:
2769 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002770 try:
2771 f = open(self.pwfile, "r")
2772 for line in f:
2773 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002774 if not line or line[0] == '#':
2775 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002776 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2777 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002778 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002779 else:
2780 result[m.group(2)] = m.group(1)
2781 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002782 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002783 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002784 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002785 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002786
2787
Dan Albert8e0178d2015-01-27 15:53:15 -08002788def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2789 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002790
2791 # http://b/18015246
2792 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2793 # for files larger than 2GiB. We can work around this by adjusting their
2794 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2795 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2796 # it isn't clear to me exactly what circumstances cause this).
2797 # `zipfile.write()` must be used directly to work around this.
2798 #
2799 # This mess can be avoided if we port to python3.
2800 saved_zip64_limit = zipfile.ZIP64_LIMIT
2801 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2802
2803 if compress_type is None:
2804 compress_type = zip_file.compression
2805 if arcname is None:
2806 arcname = filename
2807
2808 saved_stat = os.stat(filename)
2809
2810 try:
2811 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2812 # file to be zipped and reset it when we're done.
2813 os.chmod(filename, perms)
2814
2815 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002816 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2817 # intentional. zip stores datetimes in local time without a time zone
2818 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2819 # in the zip archive.
2820 local_epoch = datetime.datetime.fromtimestamp(0)
2821 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002822 os.utime(filename, (timestamp, timestamp))
2823
2824 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2825 finally:
2826 os.chmod(filename, saved_stat.st_mode)
2827 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2828 zipfile.ZIP64_LIMIT = saved_zip64_limit
2829
2830
Tao Bao58c1b962015-05-20 09:32:18 -07002831def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002832 compress_type=None):
2833 """Wrap zipfile.writestr() function to work around the zip64 limit.
2834
2835 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2836 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2837 when calling crc32(bytes).
2838
2839 But it still works fine to write a shorter string into a large zip file.
2840 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2841 when we know the string won't be too long.
2842 """
2843
2844 saved_zip64_limit = zipfile.ZIP64_LIMIT
2845 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2846
2847 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2848 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002849 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002850 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002851 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002852 else:
Tao Baof3282b42015-04-01 11:21:55 -07002853 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002854 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2855 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2856 # such a case (since
2857 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2858 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2859 # permission bits. We follow the logic in Python 3 to get consistent
2860 # behavior between using the two versions.
2861 if not zinfo.external_attr:
2862 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002863
2864 # If compress_type is given, it overrides the value in zinfo.
2865 if compress_type is not None:
2866 zinfo.compress_type = compress_type
2867
Tao Bao58c1b962015-05-20 09:32:18 -07002868 # If perms is given, it has a priority.
2869 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002870 # If perms doesn't set the file type, mark it as a regular file.
2871 if perms & 0o770000 == 0:
2872 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002873 zinfo.external_attr = perms << 16
2874
Tao Baof3282b42015-04-01 11:21:55 -07002875 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002876 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2877
Dan Albert8b72aef2015-03-23 19:13:21 -07002878 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002879 zipfile.ZIP64_LIMIT = saved_zip64_limit
2880
2881
Kelvin Zhang1caead02022-09-23 10:06:03 -07002882def ZipDelete(zip_filename, entries, force=False):
Tao Bao89d7ab22017-12-14 17:05:33 -08002883 """Deletes entries from a ZIP file.
2884
Tao Bao89d7ab22017-12-14 17:05:33 -08002885 Args:
2886 zip_filename: The name of the ZIP file.
2887 entries: The name of the entry, or the list of names to be deleted.
Tao Bao89d7ab22017-12-14 17:05:33 -08002888 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002889 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002890 entries = [entries]
Kelvin Zhang70876142022-02-09 16:05:29 -08002891 # If list is empty, nothing to do
2892 if not entries:
2893 return
Wei Li8895f9e2022-10-10 17:13:17 -07002894
2895 with zipfile.ZipFile(zip_filename, 'r') as zin:
2896 if not force and len(set(zin.namelist()).intersection(entries)) == 0:
2897 raise ExternalError(
2898 "Failed to delete zip entries, name not matched: %s" % entries)
2899
2900 fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
2901 os.close(fd)
2902
2903 with zipfile.ZipFile(new_zipfile, 'w') as zout:
2904 for item in zin.infolist():
2905 if item.filename in entries:
2906 continue
2907 buffer = zin.read(item.filename)
2908 zout.writestr(item, buffer)
2909
2910 os.replace(new_zipfile, zip_filename)
Tao Bao89d7ab22017-12-14 17:05:33 -08002911
2912
Tao Baof3282b42015-04-01 11:21:55 -07002913def ZipClose(zip_file):
2914 # http://b/18015246
2915 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2916 # central directory.
2917 saved_zip64_limit = zipfile.ZIP64_LIMIT
2918 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2919
2920 zip_file.close()
2921
2922 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002923
2924
2925class DeviceSpecificParams(object):
2926 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002927
Doug Zongker05d3dea2009-06-22 11:32:31 -07002928 def __init__(self, **kwargs):
2929 """Keyword arguments to the constructor become attributes of this
2930 object, which is passed to all functions in the device-specific
2931 module."""
Tao Bao38884282019-07-10 22:20:56 -07002932 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002933 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002934 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002935
2936 if self.module is None:
2937 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002938 if not path:
2939 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002940 try:
2941 if os.path.isdir(path):
2942 info = imp.find_module("releasetools", [path])
2943 else:
2944 d, f = os.path.split(path)
2945 b, x = os.path.splitext(f)
2946 if x == ".py":
2947 f = b
2948 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002949 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002950 self.module = imp.load_module("device_specific", *info)
2951 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002952 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002953
2954 def _DoCall(self, function_name, *args, **kwargs):
2955 """Call the named function in the device-specific module, passing
2956 the given args and kwargs. The first argument to the call will be
2957 the DeviceSpecific object itself. If there is no module, or the
2958 module does not define the function, return the value of the
2959 'default' kwarg (which itself defaults to None)."""
2960 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002961 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002962 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2963
2964 def FullOTA_Assertions(self):
2965 """Called after emitting the block of assertions at the top of a
2966 full OTA package. Implementations can add whatever additional
2967 assertions they like."""
2968 return self._DoCall("FullOTA_Assertions")
2969
Doug Zongkere5ff5902012-01-17 10:55:37 -08002970 def FullOTA_InstallBegin(self):
2971 """Called at the start of full OTA installation."""
2972 return self._DoCall("FullOTA_InstallBegin")
2973
Yifan Hong10c530d2018-12-27 17:34:18 -08002974 def FullOTA_GetBlockDifferences(self):
2975 """Called during full OTA installation and verification.
2976 Implementation should return a list of BlockDifference objects describing
2977 the update on each additional partitions.
2978 """
2979 return self._DoCall("FullOTA_GetBlockDifferences")
2980
Doug Zongker05d3dea2009-06-22 11:32:31 -07002981 def FullOTA_InstallEnd(self):
2982 """Called at the end of full OTA installation; typically this is
2983 used to install the image for the device's baseband processor."""
2984 return self._DoCall("FullOTA_InstallEnd")
2985
2986 def IncrementalOTA_Assertions(self):
2987 """Called after emitting the block of assertions at the top of an
2988 incremental OTA package. Implementations can add whatever
2989 additional assertions they like."""
2990 return self._DoCall("IncrementalOTA_Assertions")
2991
Doug Zongkere5ff5902012-01-17 10:55:37 -08002992 def IncrementalOTA_VerifyBegin(self):
2993 """Called at the start of the verification phase of incremental
2994 OTA installation; additional checks can be placed here to abort
2995 the script before any changes are made."""
2996 return self._DoCall("IncrementalOTA_VerifyBegin")
2997
Doug Zongker05d3dea2009-06-22 11:32:31 -07002998 def IncrementalOTA_VerifyEnd(self):
2999 """Called at the end of the verification phase of incremental OTA
3000 installation; additional checks can be placed here to abort the
3001 script before any changes are made."""
3002 return self._DoCall("IncrementalOTA_VerifyEnd")
3003
Doug Zongkere5ff5902012-01-17 10:55:37 -08003004 def IncrementalOTA_InstallBegin(self):
3005 """Called at the start of incremental OTA installation (after
3006 verification is complete)."""
3007 return self._DoCall("IncrementalOTA_InstallBegin")
3008
Yifan Hong10c530d2018-12-27 17:34:18 -08003009 def IncrementalOTA_GetBlockDifferences(self):
3010 """Called during incremental OTA installation and verification.
3011 Implementation should return a list of BlockDifference objects describing
3012 the update on each additional partitions.
3013 """
3014 return self._DoCall("IncrementalOTA_GetBlockDifferences")
3015
Doug Zongker05d3dea2009-06-22 11:32:31 -07003016 def IncrementalOTA_InstallEnd(self):
3017 """Called at the end of incremental OTA installation; typically
3018 this is used to install the image for the device's baseband
3019 processor."""
3020 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003021
Tao Bao9bc6bb22015-11-09 16:58:28 -08003022 def VerifyOTA_Assertions(self):
3023 return self._DoCall("VerifyOTA_Assertions")
3024
Tao Bao76def242017-11-21 09:25:31 -08003025
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003026class File(object):
Tao Bao76def242017-11-21 09:25:31 -08003027 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003028 self.name = name
3029 self.data = data
3030 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09003031 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08003032 self.sha1 = sha1(data).hexdigest()
3033
3034 @classmethod
3035 def FromLocalFile(cls, name, diskname):
3036 f = open(diskname, "rb")
3037 data = f.read()
3038 f.close()
3039 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003040
3041 def WriteToTemp(self):
3042 t = tempfile.NamedTemporaryFile()
3043 t.write(self.data)
3044 t.flush()
3045 return t
3046
Dan Willemsen2ee00d52017-03-05 19:51:56 -08003047 def WriteToDir(self, d):
3048 with open(os.path.join(d, self.name), "wb") as fp:
3049 fp.write(self.data)
3050
Geremy Condra36bd3652014-02-06 19:45:10 -08003051 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07003052 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003053
Tao Bao76def242017-11-21 09:25:31 -08003054
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003055DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04003056 ".gz": "imgdiff",
3057 ".zip": ["imgdiff", "-z"],
3058 ".jar": ["imgdiff", "-z"],
3059 ".apk": ["imgdiff", "-z"],
3060 ".img": "imgdiff",
3061}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003062
Tao Bao76def242017-11-21 09:25:31 -08003063
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003064class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07003065 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003066 self.tf = tf
3067 self.sf = sf
3068 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07003069 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003070
3071 def ComputePatch(self):
3072 """Compute the patch (as a string of data) needed to turn sf into
3073 tf. Returns the same tuple as GetPatch()."""
3074
3075 tf = self.tf
3076 sf = self.sf
3077
Doug Zongker24cd2802012-08-14 16:36:15 -07003078 if self.diff_program:
3079 diff_program = self.diff_program
3080 else:
3081 ext = os.path.splitext(tf.name)[1]
3082 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003083
3084 ttemp = tf.WriteToTemp()
3085 stemp = sf.WriteToTemp()
3086
3087 ext = os.path.splitext(tf.name)[1]
3088
3089 try:
3090 ptemp = tempfile.NamedTemporaryFile()
3091 if isinstance(diff_program, list):
3092 cmd = copy.copy(diff_program)
3093 else:
3094 cmd = [diff_program]
3095 cmd.append(stemp.name)
3096 cmd.append(ttemp.name)
3097 cmd.append(ptemp.name)
3098 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003099 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003100
Doug Zongkerf8340082014-08-05 10:39:37 -07003101 def run():
3102 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003103 if e:
3104 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003105 th = threading.Thread(target=run)
3106 th.start()
3107 th.join(timeout=300) # 5 mins
3108 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003109 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003110 p.terminate()
3111 th.join(5)
3112 if th.is_alive():
3113 p.kill()
3114 th.join()
3115
Tianjie Xua2a9f992018-01-05 15:15:54 -08003116 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07003117 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003118 self.patch = None
3119 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003120 diff = ptemp.read()
3121 finally:
3122 ptemp.close()
3123 stemp.close()
3124 ttemp.close()
3125
3126 self.patch = diff
3127 return self.tf, self.sf, self.patch
3128
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003129 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003130 """Returns a tuple of (target_file, source_file, patch_data).
3131
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003132 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003133 computing the patch failed.
3134 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003135 return self.tf, self.sf, self.patch
3136
3137
3138def ComputeDifferences(diffs):
3139 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003140 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003141
3142 # Do the largest files first, to try and reduce the long-pole effect.
3143 by_size = [(i.tf.size, i) for i in diffs]
3144 by_size.sort(reverse=True)
3145 by_size = [i[1] for i in by_size]
3146
3147 lock = threading.Lock()
3148 diff_iter = iter(by_size) # accessed under lock
3149
3150 def worker():
3151 try:
3152 lock.acquire()
3153 for d in diff_iter:
3154 lock.release()
3155 start = time.time()
3156 d.ComputePatch()
3157 dur = time.time() - start
3158 lock.acquire()
3159
3160 tf, sf, patch = d.GetPatch()
3161 if sf.name == tf.name:
3162 name = tf.name
3163 else:
3164 name = "%s (%s)" % (tf.name, sf.name)
3165 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003166 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003167 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003168 logger.info(
3169 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3170 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003171 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003172 except Exception:
3173 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003174 raise
3175
3176 # start worker threads; wait for them all to finish.
3177 threads = [threading.Thread(target=worker)
3178 for i in range(OPTIONS.worker_threads)]
3179 for th in threads:
3180 th.start()
3181 while threads:
3182 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003183
3184
Dan Albert8b72aef2015-03-23 19:13:21 -07003185class BlockDifference(object):
3186 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003187 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003188 self.tgt = tgt
3189 self.src = src
3190 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003191 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003192 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003193
Tao Baodd2a5892015-03-12 12:32:37 -07003194 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003195 version = max(
3196 int(i) for i in
3197 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003198 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003199 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003200
Tianjie Xu41976c72019-07-03 13:57:01 -07003201 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3202 version=self.version,
3203 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003204 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003205 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003206 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003207 self.touched_src_ranges = b.touched_src_ranges
3208 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003209
Yifan Hong10c530d2018-12-27 17:34:18 -08003210 # On devices with dynamic partitions, for new partitions,
3211 # src is None but OPTIONS.source_info_dict is not.
3212 if OPTIONS.source_info_dict is None:
3213 is_dynamic_build = OPTIONS.info_dict.get(
3214 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003215 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003216 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003217 is_dynamic_build = OPTIONS.source_info_dict.get(
3218 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003219 is_dynamic_source = partition in shlex.split(
3220 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003221
Yifan Hongbb2658d2019-01-25 12:30:58 -08003222 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003223 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3224
Yifan Hongbb2658d2019-01-25 12:30:58 -08003225 # For dynamic partitions builds, check partition list in both source
3226 # and target build because new partitions may be added, and existing
3227 # partitions may be removed.
3228 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3229
Yifan Hong10c530d2018-12-27 17:34:18 -08003230 if is_dynamic:
3231 self.device = 'map_partition("%s")' % partition
3232 else:
3233 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003234 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3235 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003236 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003237 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3238 OPTIONS.source_info_dict)
3239 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003240
Tao Baod8d14be2016-02-04 14:26:02 -08003241 @property
3242 def required_cache(self):
3243 return self._required_cache
3244
Tao Bao76def242017-11-21 09:25:31 -08003245 def WriteScript(self, script, output_zip, progress=None,
3246 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003247 if not self.src:
3248 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003249 script.Print("Patching %s image unconditionally..." % (self.partition,))
3250 else:
3251 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003252
Dan Albert8b72aef2015-03-23 19:13:21 -07003253 if progress:
3254 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003255 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003256
3257 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003258 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003259
Tao Bao9bc6bb22015-11-09 16:58:28 -08003260 def WriteStrictVerifyScript(self, script):
3261 """Verify all the blocks in the care_map, including clobbered blocks.
3262
3263 This differs from the WriteVerifyScript() function: a) it prints different
3264 error messages; b) it doesn't allow half-way updated images to pass the
3265 verification."""
3266
3267 partition = self.partition
3268 script.Print("Verifying %s..." % (partition,))
3269 ranges = self.tgt.care_map
3270 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003271 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003272 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3273 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003274 self.device, ranges_str,
3275 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003276 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003277 script.AppendExtra("")
3278
Tao Baod522bdc2016-04-12 15:53:16 -07003279 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003280 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003281
3282 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003283 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003284 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003285
3286 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003287 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003288 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003289 ranges = self.touched_src_ranges
3290 expected_sha1 = self.touched_src_sha1
3291 else:
3292 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3293 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003294
3295 # No blocks to be checked, skipping.
3296 if not ranges:
3297 return
3298
Tao Bao5ece99d2015-05-12 11:42:31 -07003299 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003300 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003301 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003302 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3303 '"%s.patch.dat")) then' % (
3304 self.device, ranges_str, expected_sha1,
3305 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003306 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003307 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003308
Tianjie Xufc3422a2015-12-15 11:53:59 -08003309 if self.version >= 4:
3310
3311 # Bug: 21124327
3312 # When generating incrementals for the system and vendor partitions in
3313 # version 4 or newer, explicitly check the first block (which contains
3314 # the superblock) of the partition to see if it's what we expect. If
3315 # this check fails, give an explicit log message about the partition
3316 # having been remounted R/W (the most likely explanation).
3317 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003318 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003319
3320 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003321 if partition == "system":
3322 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3323 else:
3324 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003325 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003326 'ifelse (block_image_recover({device}, "{ranges}") && '
3327 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003328 'package_extract_file("{partition}.transfer.list"), '
3329 '"{partition}.new.dat", "{partition}.patch.dat"), '
3330 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003331 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003332 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003333 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003334
Tao Baodd2a5892015-03-12 12:32:37 -07003335 # Abort the OTA update. Note that the incremental OTA cannot be applied
3336 # even if it may match the checksum of the target partition.
3337 # a) If version < 3, operations like move and erase will make changes
3338 # unconditionally and damage the partition.
3339 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003340 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003341 if partition == "system":
3342 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3343 else:
3344 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3345 script.AppendExtra((
3346 'abort("E%d: %s partition has unexpected contents");\n'
3347 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003348
Yifan Hong10c530d2018-12-27 17:34:18 -08003349 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003350 partition = self.partition
3351 script.Print('Verifying the updated %s image...' % (partition,))
3352 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3353 ranges = self.tgt.care_map
3354 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003355 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003356 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003357 self.device, ranges_str,
3358 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003359
3360 # Bug: 20881595
3361 # Verify that extended blocks are really zeroed out.
3362 if self.tgt.extended:
3363 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003364 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003365 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003366 self.device, ranges_str,
3367 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003368 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003369 if partition == "system":
3370 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3371 else:
3372 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003373 script.AppendExtra(
3374 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003375 ' abort("E%d: %s partition has unexpected non-zero contents after '
3376 'OTA update");\n'
3377 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003378 else:
3379 script.Print('Verified the updated %s image.' % (partition,))
3380
Tianjie Xu209db462016-05-24 17:34:52 -07003381 if partition == "system":
3382 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3383 else:
3384 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3385
Tao Bao5fcaaef2015-06-01 13:40:49 -07003386 script.AppendExtra(
3387 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003388 ' abort("E%d: %s partition has unexpected contents after OTA '
3389 'update");\n'
3390 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003391
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003392 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003393 ZipWrite(output_zip,
3394 '{}.transfer.list'.format(self.path),
3395 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003396
Tao Bao76def242017-11-21 09:25:31 -08003397 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3398 # its size. Quailty 9 almost triples the compression time but doesn't
3399 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003400 # zip | brotli(quality 6) | brotli(quality 9)
3401 # compressed_size: 942M | 869M (~8% reduced) | 854M
3402 # compression_time: 75s | 265s | 719s
3403 # decompression_time: 15s | 25s | 25s
3404
3405 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003406 brotli_cmd = ['brotli', '--quality=6',
3407 '--output={}.new.dat.br'.format(self.path),
3408 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003409 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003410 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003411
3412 new_data_name = '{}.new.dat.br'.format(self.partition)
3413 ZipWrite(output_zip,
3414 '{}.new.dat.br'.format(self.path),
3415 new_data_name,
3416 compress_type=zipfile.ZIP_STORED)
3417 else:
3418 new_data_name = '{}.new.dat'.format(self.partition)
3419 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3420
Dan Albert8e0178d2015-01-27 15:53:15 -08003421 ZipWrite(output_zip,
3422 '{}.patch.dat'.format(self.path),
3423 '{}.patch.dat'.format(self.partition),
3424 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003425
Tianjie Xu209db462016-05-24 17:34:52 -07003426 if self.partition == "system":
3427 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3428 else:
3429 code = ErrorCode.VENDOR_UPDATE_FAILURE
3430
Yifan Hong10c530d2018-12-27 17:34:18 -08003431 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003432 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003433 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003434 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003435 device=self.device, partition=self.partition,
3436 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003437 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003438
Kelvin Zhang0876c412020-06-23 15:06:58 -04003439 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003440 data = source.ReadRangeSet(ranges)
3441 ctx = sha1()
3442
3443 for p in data:
3444 ctx.update(p)
3445
3446 return ctx.hexdigest()
3447
Kelvin Zhang0876c412020-06-23 15:06:58 -04003448 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003449 """Return the hash value for all zero blocks."""
3450 zero_block = '\x00' * 4096
3451 ctx = sha1()
3452 for _ in range(num_blocks):
3453 ctx.update(zero_block)
3454
3455 return ctx.hexdigest()
3456
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003457
Tianjie Xu41976c72019-07-03 13:57:01 -07003458# Expose these two classes to support vendor-specific scripts
3459DataImage = images.DataImage
3460EmptyImage = images.EmptyImage
3461
Tao Bao76def242017-11-21 09:25:31 -08003462
Doug Zongker96a57e72010-09-26 14:57:41 -07003463# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003464PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003465 "ext4": "EMMC",
3466 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003467 "f2fs": "EMMC",
Tim Zimmermanna06f8332022-10-01 11:56:57 +02003468 "squashfs": "EMMC",
3469 "erofs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003470}
Doug Zongker96a57e72010-09-26 14:57:41 -07003471
Kelvin Zhang0876c412020-06-23 15:06:58 -04003472
Yifan Hongbdb32012020-05-07 12:38:53 -07003473def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3474 """
3475 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3476 backwards compatibility. It aborts if the fstab entry has slotselect option
3477 (unless check_no_slot is explicitly set to False).
3478 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003479 fstab = info["fstab"]
3480 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003481 if check_no_slot:
3482 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003483 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003484 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3485 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003486 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003487
3488
Yifan Hongbdb32012020-05-07 12:38:53 -07003489def GetTypeAndDeviceExpr(mount_point, info):
3490 """
3491 Return the filesystem of the partition, and an edify expression that evaluates
3492 to the device at runtime.
3493 """
3494 fstab = info["fstab"]
3495 if fstab:
3496 p = fstab[mount_point]
3497 device_expr = '"%s"' % fstab[mount_point].device
3498 if p.slotselect:
3499 device_expr = 'add_slot_suffix(%s)' % device_expr
3500 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003501 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003502
3503
3504def GetEntryForDevice(fstab, device):
3505 """
3506 Returns:
3507 The first entry in fstab whose device is the given value.
3508 """
3509 if not fstab:
3510 return None
3511 for mount_point in fstab:
3512 if fstab[mount_point].device == device:
3513 return fstab[mount_point]
3514 return None
3515
Kelvin Zhang0876c412020-06-23 15:06:58 -04003516
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003517def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003518 """Parses and converts a PEM-encoded certificate into DER-encoded.
3519
3520 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3521
3522 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003523 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003524 """
3525 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003526 save = False
3527 for line in data.split("\n"):
3528 if "--END CERTIFICATE--" in line:
3529 break
3530 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003531 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003532 if "--BEGIN CERTIFICATE--" in line:
3533 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003534 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003535 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003536
Tao Bao04e1f012018-02-04 12:13:35 -08003537
3538def ExtractPublicKey(cert):
3539 """Extracts the public key (PEM-encoded) from the given certificate file.
3540
3541 Args:
3542 cert: The certificate filename.
3543
3544 Returns:
3545 The public key string.
3546
3547 Raises:
3548 AssertionError: On non-zero return from 'openssl'.
3549 """
3550 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3551 # While openssl 1.1 writes the key into the given filename followed by '-out',
3552 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3553 # stdout instead.
3554 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3555 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3556 pubkey, stderrdata = proc.communicate()
3557 assert proc.returncode == 0, \
3558 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3559 return pubkey
3560
3561
Tao Bao1ac886e2019-06-26 11:58:22 -07003562def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003563 """Extracts the AVB public key from the given public or private key.
3564
3565 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003566 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003567 key: The input key file, which should be PEM-encoded public or private key.
3568
3569 Returns:
3570 The path to the extracted AVB public key file.
3571 """
3572 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3573 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003574 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003575 return output
3576
3577
Doug Zongker412c02f2014-02-13 10:58:24 -08003578def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3579 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003580 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003581
Tao Bao6d5d6232018-03-09 17:04:42 -08003582 Most of the space in the boot and recovery images is just the kernel, which is
3583 identical for the two, so the resulting patch should be efficient. Add it to
3584 the output zip, along with a shell script that is run from init.rc on first
3585 boot to actually do the patching and install the new recovery image.
3586
3587 Args:
3588 input_dir: The top-level input directory of the target-files.zip.
3589 output_sink: The callback function that writes the result.
3590 recovery_img: File object for the recovery image.
3591 boot_img: File objects for the boot image.
3592 info_dict: A dict returned by common.LoadInfoDict() on the input
3593 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003594 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003595 if info_dict is None:
3596 info_dict = OPTIONS.info_dict
3597
Tao Bao6d5d6232018-03-09 17:04:42 -08003598 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003599 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3600
3601 if board_uses_vendorimage:
3602 # In this case, the output sink is rooted at VENDOR
3603 recovery_img_path = "etc/recovery.img"
3604 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3605 sh_dir = "bin"
3606 else:
3607 # In this case the output sink is rooted at SYSTEM
3608 recovery_img_path = "vendor/etc/recovery.img"
3609 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3610 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003611
Tao Baof2cffbd2015-07-22 12:33:18 -07003612 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003613 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003614
3615 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003616 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003617 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003618 # With system-root-image, boot and recovery images will have mismatching
3619 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3620 # to handle such a case.
3621 if system_root_image:
3622 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003623 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003624 assert not os.path.exists(path)
3625 else:
3626 diff_program = ["imgdiff"]
3627 if os.path.exists(path):
3628 diff_program.append("-b")
3629 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003630 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003631 else:
3632 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003633
3634 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3635 _, _, patch = d.ComputePatch()
3636 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003637
Dan Albertebb19aa2015-03-27 19:11:53 -07003638 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003639 # The following GetTypeAndDevice()s need to use the path in the target
3640 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003641 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3642 check_no_slot=False)
3643 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3644 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003645 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003646 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003647
Tao Baof2cffbd2015-07-22 12:33:18 -07003648 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003649
3650 # Note that we use /vendor to refer to the recovery resources. This will
3651 # work for a separate vendor partition mounted at /vendor or a
3652 # /system/vendor subdirectory on the system partition, for which init will
3653 # create a symlink from /vendor to /system/vendor.
3654
3655 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003656if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3657 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003658 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003659 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3660 log -t recovery "Installing new recovery image: succeeded" || \\
3661 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003662else
3663 log -t recovery "Recovery image already installed"
3664fi
3665""" % {'type': recovery_type,
3666 'device': recovery_device,
3667 'sha1': recovery_img.sha1,
3668 'size': recovery_img.size}
3669 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003670 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003671if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3672 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003673 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003674 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3675 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3676 log -t recovery "Installing new recovery image: succeeded" || \\
3677 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003678else
3679 log -t recovery "Recovery image already installed"
3680fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003681""" % {'boot_size': boot_img.size,
3682 'boot_sha1': boot_img.sha1,
3683 'recovery_size': recovery_img.size,
3684 'recovery_sha1': recovery_img.sha1,
3685 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003686 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003687 'recovery_type': recovery_type,
3688 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003689 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003690
Bill Peckhame868aec2019-09-17 17:06:47 -07003691 # The install script location moved from /system/etc to /system/bin in the L
3692 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3693 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003694
Tao Bao32fcdab2018-10-12 10:30:39 -07003695 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003696
Tao Baoda30cfa2017-12-01 16:19:46 -08003697 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003698
3699
3700class DynamicPartitionUpdate(object):
3701 def __init__(self, src_group=None, tgt_group=None, progress=None,
3702 block_difference=None):
3703 self.src_group = src_group
3704 self.tgt_group = tgt_group
3705 self.progress = progress
3706 self.block_difference = block_difference
3707
3708 @property
3709 def src_size(self):
3710 if not self.block_difference:
3711 return 0
3712 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3713
3714 @property
3715 def tgt_size(self):
3716 if not self.block_difference:
3717 return 0
3718 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3719
3720 @staticmethod
3721 def _GetSparseImageSize(img):
3722 if not img:
3723 return 0
3724 return img.blocksize * img.total_blocks
3725
3726
3727class DynamicGroupUpdate(object):
3728 def __init__(self, src_size=None, tgt_size=None):
3729 # None: group does not exist. 0: no size limits.
3730 self.src_size = src_size
3731 self.tgt_size = tgt_size
3732
3733
3734class DynamicPartitionsDifference(object):
3735 def __init__(self, info_dict, block_diffs, progress_dict=None,
3736 source_info_dict=None):
3737 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003738 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003739
3740 self._remove_all_before_apply = False
3741 if source_info_dict is None:
3742 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003743 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003744
Tao Baof1113e92019-06-18 12:10:14 -07003745 block_diff_dict = collections.OrderedDict(
3746 [(e.partition, e) for e in block_diffs])
3747
Yifan Hong10c530d2018-12-27 17:34:18 -08003748 assert len(block_diff_dict) == len(block_diffs), \
3749 "Duplicated BlockDifference object for {}".format(
3750 [partition for partition, count in
3751 collections.Counter(e.partition for e in block_diffs).items()
3752 if count > 1])
3753
Yifan Hong79997e52019-01-23 16:56:19 -08003754 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003755
3756 for p, block_diff in block_diff_dict.items():
3757 self._partition_updates[p] = DynamicPartitionUpdate()
3758 self._partition_updates[p].block_difference = block_diff
3759
3760 for p, progress in progress_dict.items():
3761 if p in self._partition_updates:
3762 self._partition_updates[p].progress = progress
3763
3764 tgt_groups = shlex.split(info_dict.get(
3765 "super_partition_groups", "").strip())
3766 src_groups = shlex.split(source_info_dict.get(
3767 "super_partition_groups", "").strip())
3768
3769 for g in tgt_groups:
3770 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003771 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003772 assert p in self._partition_updates, \
3773 "{} is in target super_{}_partition_list but no BlockDifference " \
3774 "object is provided.".format(p, g)
3775 self._partition_updates[p].tgt_group = g
3776
3777 for g in src_groups:
3778 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003779 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003780 assert p in self._partition_updates, \
3781 "{} is in source super_{}_partition_list but no BlockDifference " \
3782 "object is provided.".format(p, g)
3783 self._partition_updates[p].src_group = g
3784
Yifan Hong45433e42019-01-18 13:55:25 -08003785 target_dynamic_partitions = set(shlex.split(info_dict.get(
3786 "dynamic_partition_list", "").strip()))
3787 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3788 if u.tgt_size)
3789 assert block_diffs_with_target == target_dynamic_partitions, \
3790 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3791 list(target_dynamic_partitions), list(block_diffs_with_target))
3792
3793 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3794 "dynamic_partition_list", "").strip()))
3795 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3796 if u.src_size)
3797 assert block_diffs_with_source == source_dynamic_partitions, \
3798 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3799 list(source_dynamic_partitions), list(block_diffs_with_source))
3800
Yifan Hong10c530d2018-12-27 17:34:18 -08003801 if self._partition_updates:
3802 logger.info("Updating dynamic partitions %s",
3803 self._partition_updates.keys())
3804
Yifan Hong79997e52019-01-23 16:56:19 -08003805 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003806
3807 for g in tgt_groups:
3808 self._group_updates[g] = DynamicGroupUpdate()
3809 self._group_updates[g].tgt_size = int(info_dict.get(
3810 "super_%s_group_size" % g, "0").strip())
3811
3812 for g in src_groups:
3813 if g not in self._group_updates:
3814 self._group_updates[g] = DynamicGroupUpdate()
3815 self._group_updates[g].src_size = int(source_info_dict.get(
3816 "super_%s_group_size" % g, "0").strip())
3817
3818 self._Compute()
3819
3820 def WriteScript(self, script, output_zip, write_verify_script=False):
3821 script.Comment('--- Start patching dynamic partitions ---')
3822 for p, u in self._partition_updates.items():
3823 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3824 script.Comment('Patch partition %s' % p)
3825 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3826 write_verify_script=False)
3827
3828 op_list_path = MakeTempFile()
3829 with open(op_list_path, 'w') as f:
3830 for line in self._op_list:
3831 f.write('{}\n'.format(line))
3832
3833 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3834
3835 script.Comment('Update dynamic partition metadata')
3836 script.AppendExtra('assert(update_dynamic_partitions('
3837 'package_extract_file("dynamic_partitions_op_list")));')
3838
3839 if write_verify_script:
3840 for p, u in self._partition_updates.items():
3841 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3842 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003843 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003844
3845 for p, u in self._partition_updates.items():
3846 if u.tgt_size and u.src_size <= u.tgt_size:
3847 script.Comment('Patch partition %s' % p)
3848 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3849 write_verify_script=write_verify_script)
3850 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003851 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003852
3853 script.Comment('--- End patching dynamic partitions ---')
3854
3855 def _Compute(self):
3856 self._op_list = list()
3857
3858 def append(line):
3859 self._op_list.append(line)
3860
3861 def comment(line):
3862 self._op_list.append("# %s" % line)
3863
3864 if self._remove_all_before_apply:
3865 comment('Remove all existing dynamic partitions and groups before '
3866 'applying full OTA')
3867 append('remove_all_groups')
3868
3869 for p, u in self._partition_updates.items():
3870 if u.src_group and not u.tgt_group:
3871 append('remove %s' % p)
3872
3873 for p, u in self._partition_updates.items():
3874 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3875 comment('Move partition %s from %s to default' % (p, u.src_group))
3876 append('move %s default' % p)
3877
3878 for p, u in self._partition_updates.items():
3879 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3880 comment('Shrink partition %s from %d to %d' %
3881 (p, u.src_size, u.tgt_size))
3882 append('resize %s %s' % (p, u.tgt_size))
3883
3884 for g, u in self._group_updates.items():
3885 if u.src_size is not None and u.tgt_size is None:
3886 append('remove_group %s' % g)
3887 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003888 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003889 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3890 append('resize_group %s %d' % (g, u.tgt_size))
3891
3892 for g, u in self._group_updates.items():
3893 if u.src_size is None and u.tgt_size is not None:
3894 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3895 append('add_group %s %d' % (g, u.tgt_size))
3896 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003897 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003898 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3899 append('resize_group %s %d' % (g, u.tgt_size))
3900
3901 for p, u in self._partition_updates.items():
3902 if u.tgt_group and not u.src_group:
3903 comment('Add partition %s to group %s' % (p, u.tgt_group))
3904 append('add %s %s' % (p, u.tgt_group))
3905
3906 for p, u in self._partition_updates.items():
3907 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003908 comment('Grow partition %s from %d to %d' %
3909 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003910 append('resize %s %d' % (p, u.tgt_size))
3911
3912 for p, u in self._partition_updates.items():
3913 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3914 comment('Move partition %s from default to %s' %
3915 (p, u.tgt_group))
3916 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003917
3918
jiajia tangf3f842b2021-03-17 21:49:44 +08003919def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003920 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003921 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003922
3923 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003924 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003925
3926 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003927 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003928 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003929 tmp_dir = MakeTempDir('boot_', suffix='.img')
3930 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003931 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3932 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003933 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3934 if not os.path.isfile(ramdisk):
3935 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3936 return None
3937 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003938 if ramdisk_format == RamdiskFormat.LZ4:
3939 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3940 elif ramdisk_format == RamdiskFormat.GZ:
3941 with open(ramdisk, 'rb') as input_stream:
3942 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003943 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3944 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003945 p2.wait()
3946 else:
3947 logger.error('Only support lz4 or minigzip ramdisk format.')
3948 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003949
3950 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3951 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3952 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3953 # the host environment.
3954 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003955 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003956
Yifan Hongc65a0542021-01-07 14:21:01 -08003957 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3958 prop_file = os.path.join(extracted_ramdisk, search_path)
3959 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003960 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003961 logger.warning(
3962 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003963
Yifan Hong7dc51172021-01-12 11:27:39 -08003964 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003965
Yifan Hong85ac5012021-01-07 14:43:46 -08003966 except ExternalError as e:
3967 logger.warning('Unable to get boot image build props: %s', e)
3968 return None
3969
3970
3971def GetBootImageTimestamp(boot_img):
3972 """
3973 Get timestamp from ramdisk within the boot image
3974
3975 Args:
3976 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3977
3978 Return:
3979 An integer that corresponds to the timestamp of the boot image, or None
3980 if file has unknown format. Raise exception if an unexpected error has
3981 occurred.
3982 """
3983 prop_file = GetBootImageBuildProp(boot_img)
3984 if not prop_file:
3985 return None
3986
3987 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3988 if props is None:
3989 return None
3990
3991 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003992 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3993 if timestamp:
3994 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003995 logger.warning(
3996 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003997 return None
3998
3999 except ExternalError as e:
4000 logger.warning('Unable to get boot image timestamp: %s', e)
4001 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04004002
4003
Kelvin Zhang26390482021-11-02 14:31:10 -07004004def IsSparseImage(filepath):
Kelvin Zhang1caead02022-09-23 10:06:03 -07004005 if not os.path.exists(filepath):
4006 return False
Kelvin Zhang26390482021-11-02 14:31:10 -07004007 with open(filepath, 'rb') as fp:
4008 # Magic for android sparse image format
4009 # https://source.android.com/devices/bootloader/images
4010 return fp.read(4) == b'\x3A\xFF\x26\xED'