blob: b6ed8a4f42c7b5f20473b0082ae8f82698640dec [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800112# descriptor into vbmeta.img. When adding a new entry here, the
113# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
114# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000115AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
116 'system', 'system_ext', 'vendor', 'vendor_boot',
117 'vendor_dlkm', 'odm_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',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400131]
Tianjie Xu861f4132018-09-12 11:49:33 -0700132
Yifan Hong5057b952021-01-07 14:09:57 -0800133# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800134PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800135
Yifan Hongc65a0542021-01-07 14:21:01 -0800136# See sysprop.mk. If file is moved, add new search paths here; don't remove
137# existing search paths.
138RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700139
Tianjie Xu209db462016-05-24 17:34:52 -0700140class ErrorCode(object):
141 """Define error_codes for failures that happen during the actual
142 update package installation.
143
144 Error codes 0-999 are reserved for failures before the package
145 installation (i.e. low battery, package verification failure).
146 Detailed code in 'bootable/recovery/error_code.h' """
147
148 SYSTEM_VERIFICATION_FAILURE = 1000
149 SYSTEM_UPDATE_FAILURE = 1001
150 SYSTEM_UNEXPECTED_CONTENTS = 1002
151 SYSTEM_NONZERO_CONTENTS = 1003
152 SYSTEM_RECOVER_FAILURE = 1004
153 VENDOR_VERIFICATION_FAILURE = 2000
154 VENDOR_UPDATE_FAILURE = 2001
155 VENDOR_UNEXPECTED_CONTENTS = 2002
156 VENDOR_NONZERO_CONTENTS = 2003
157 VENDOR_RECOVER_FAILURE = 2004
158 OEM_PROP_MISMATCH = 3000
159 FINGERPRINT_MISMATCH = 3001
160 THUMBPRINT_MISMATCH = 3002
161 OLDER_BUILD = 3003
162 DEVICE_MISMATCH = 3004
163 BAD_PATCH_FILE = 3005
164 INSUFFICIENT_CACHE_SPACE = 3006
165 TUNE_PARTITION_FAILURE = 3007
166 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800167
Tao Bao80921982018-03-21 21:02:19 -0700168
Dan Albert8b72aef2015-03-23 19:13:21 -0700169class ExternalError(RuntimeError):
170 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700171
172
Tao Bao32fcdab2018-10-12 10:30:39 -0700173def InitLogging():
174 DEFAULT_LOGGING_CONFIG = {
175 'version': 1,
176 'disable_existing_loggers': False,
177 'formatters': {
178 'standard': {
179 'format':
180 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
181 'datefmt': '%Y-%m-%d %H:%M:%S',
182 },
183 },
184 'handlers': {
185 'default': {
186 'class': 'logging.StreamHandler',
187 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700188 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700189 },
190 },
191 'loggers': {
192 '': {
193 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700195 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 }
197 }
198 }
199 env_config = os.getenv('LOGGING_CONFIG')
200 if env_config:
201 with open(env_config) as f:
202 config = json.load(f)
203 else:
204 config = DEFAULT_LOGGING_CONFIG
205
206 # Increase the logging level for verbose mode.
207 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700208 config = copy.deepcopy(config)
209 config['handlers']['default']['level'] = 'INFO'
210
211 if OPTIONS.logfile:
212 config = copy.deepcopy(config)
213 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400214 'class': 'logging.FileHandler',
215 'formatter': 'standard',
216 'level': 'INFO',
217 'mode': 'w',
218 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700219 }
220 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700221
222 logging.config.dictConfig(config)
223
224
Yifan Hong8e332ff2020-07-29 17:51:55 -0700225def SetHostToolLocation(tool_name, location):
226 OPTIONS.host_tools[tool_name] = location
227
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900228def FindHostToolPath(tool_name):
229 """Finds the path to the host tool.
230
231 Args:
232 tool_name: name of the tool to find
233 Returns:
234 path to the tool if found under either one of the host_tools map or under
235 the same directory as this binary is located at. If not found, tool_name
236 is returned.
237 """
238 if tool_name in OPTIONS.host_tools:
239 return OPTIONS.host_tools[tool_name]
240
241 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
242 tool_path = os.path.join(my_dir, tool_name)
243 if os.path.exists(tool_path):
244 return tool_path
245
246 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700247
Tao Bao39451582017-05-04 11:10:47 -0700248def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700250
Tao Bao73dd4f42018-10-04 16:25:33 -0700251 Args:
252 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700253 verbose: Whether the commands should be shown. Default to the global
254 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
256 stdin, etc. stdout and stderr will default to subprocess.PIPE and
257 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800258 universal_newlines will default to True, as most of the users in
259 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700260
261 Returns:
262 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700263 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700264 if 'stdout' not in kwargs and 'stderr' not in kwargs:
265 kwargs['stdout'] = subprocess.PIPE
266 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800267 if 'universal_newlines' not in kwargs:
268 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700269
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900270 if args:
271 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900273 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700274
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400276 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700277 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700278 return subprocess.Popen(args, **kwargs)
279
280
Tao Bao986ee862018-10-04 15:46:16 -0700281def RunAndCheckOutput(args, verbose=None, **kwargs):
282 """Runs the given command and returns the output.
283
284 Args:
285 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700286 verbose: Whether the commands should be shown. Default to the global
287 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700288 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
289 stdin, etc. stdout and stderr will default to subprocess.PIPE and
290 subprocess.STDOUT respectively unless caller specifies any of them.
291
292 Returns:
293 The output string.
294
295 Raises:
296 ExternalError: On non-zero exit from the command.
297 """
Tao Bao986ee862018-10-04 15:46:16 -0700298 proc = Run(args, verbose=verbose, **kwargs)
299 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800300 if output is None:
301 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400303 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700304 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700305 if proc.returncode != 0:
306 raise ExternalError(
307 "Failed to run command '{}' (exit code {}):\n{}".format(
308 args, proc.returncode, output))
309 return output
310
311
Tao Baoc765cca2018-01-31 17:32:40 -0800312def RoundUpTo4K(value):
313 rounded_up = value + 4095
314 return rounded_up - (rounded_up % 4096)
315
316
Ying Wang7e6d4e42010-12-13 16:25:36 -0800317def CloseInheritedPipes():
318 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
319 before doing other work."""
320 if platform.system() != "Darwin":
321 return
322 for d in range(3, 1025):
323 try:
324 stat = os.fstat(d)
325 if stat is not None:
326 pipebit = stat[0] & 0x1000
327 if pipebit != 0:
328 os.close(d)
329 except OSError:
330 pass
331
332
Tao Bao1c320f82019-10-04 23:25:12 -0700333class BuildInfo(object):
334 """A class that holds the information for a given build.
335
336 This class wraps up the property querying for a given source or target build.
337 It abstracts away the logic of handling OEM-specific properties, and caches
338 the commonly used properties such as fingerprint.
339
340 There are two types of info dicts: a) build-time info dict, which is generated
341 at build time (i.e. included in a target_files zip); b) OEM info dict that is
342 specified at package generation time (via command line argument
343 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
344 having "oem_fingerprint_properties" in build-time info dict), all the queries
345 would be answered based on build-time info dict only. Otherwise if using
346 OEM-specific properties, some of them will be calculated from two info dicts.
347
348 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800349 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700350
351 Attributes:
352 info_dict: The build-time info dict.
353 is_ab: Whether it's a build that uses A/B OTA.
354 oem_dicts: A list of OEM dicts.
355 oem_props: A list of OEM properties that should be read from OEM dicts; None
356 if the build doesn't use any OEM-specific property.
357 fingerprint: The fingerprint of the build, which would be calculated based
358 on OEM properties if applicable.
359 device: The device name, which could come from OEM dicts if applicable.
360 """
361
362 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
363 "ro.product.manufacturer", "ro.product.model",
364 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
366 "product", "odm", "vendor", "system_ext", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
368 "product", "product_services", "odm", "vendor", "system"]
369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700370
Tao Bao3ed35d32019-10-07 20:48:48 -0700371 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700372 """Initializes a BuildInfo instance with the given dicts.
373
374 Note that it only wraps up the given dicts, without making copies.
375
376 Arguments:
377 info_dict: The build-time info dict.
378 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
379 that it always uses the first dict to calculate the fingerprint or the
380 device name. The rest would be used for asserting OEM properties only
381 (e.g. one package can be installed on one of these devices).
382
383 Raises:
384 ValueError: On invalid inputs.
385 """
386 self.info_dict = info_dict
387 self.oem_dicts = oem_dicts
388
389 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700390
Hongguang Chend7c160f2020-05-03 21:24:26 -0700391 # Skip _oem_props if oem_dicts is None to use BuildInfo in
392 # sign_target_files_apks
393 if self.oem_dicts:
394 self._oem_props = info_dict.get("oem_fingerprint_properties")
395 else:
396 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700397
Daniel Normand5fe8622020-01-08 17:01:11 -0800398 def check_fingerprint(fingerprint):
399 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
400 raise ValueError(
401 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
402 "3.2.2. Build Parameters.".format(fingerprint))
403
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800405 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800406 try:
407 fingerprint = self.CalculatePartitionFingerprint(partition)
408 check_fingerprint(fingerprint)
409 self._partition_fingerprints[partition] = fingerprint
410 except ExternalError:
411 continue
412 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800413 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800414 # need a fingerprint when creating the image.
415 self._partition_fingerprints[
416 "system_other"] = self._partition_fingerprints["system"]
417
Tao Bao1c320f82019-10-04 23:25:12 -0700418 # These two should be computed only after setting self._oem_props.
419 self._device = self.GetOemProperty("ro.product.device")
420 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800421 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700422
423 @property
424 def is_ab(self):
425 return self._is_ab
426
427 @property
428 def device(self):
429 return self._device
430
431 @property
432 def fingerprint(self):
433 return self._fingerprint
434
435 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700436 def oem_props(self):
437 return self._oem_props
438
439 def __getitem__(self, key):
440 return self.info_dict[key]
441
442 def __setitem__(self, key, value):
443 self.info_dict[key] = value
444
445 def get(self, key, default=None):
446 return self.info_dict.get(key, default)
447
448 def items(self):
449 return self.info_dict.items()
450
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000451 def _GetRawBuildProp(self, prop, partition):
452 prop_file = '{}.build.prop'.format(
453 partition) if partition else 'build.prop'
454 partition_props = self.info_dict.get(prop_file)
455 if not partition_props:
456 return None
457 return partition_props.GetProp(prop)
458
Daniel Normand5fe8622020-01-08 17:01:11 -0800459 def GetPartitionBuildProp(self, prop, partition):
460 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800461
462 # Boot image uses ro.[product.]bootimage instead of boot.
Daniel Norman2d7989a2021-04-05 17:40:47 +0000463 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800464
Daniel Normand5fe8622020-01-08 17:01:11 -0800465 # If provided a partition for this property, only look within that
466 # partition's build.prop.
467 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800468 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800469 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800470 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000471
472 prop_val = self._GetRawBuildProp(prop, partition)
473 if prop_val is not None:
474 return prop_val
475 raise ExternalError("couldn't find %s in %s.build.prop" %
476 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800477
Tao Bao1c320f82019-10-04 23:25:12 -0700478 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800479 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700480 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
481 return self._ResolveRoProductBuildProp(prop)
482
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000483 prop_val = self._GetRawBuildProp(prop, None)
484 if prop_val is not None:
485 return prop_val
486
487 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700488
489 def _ResolveRoProductBuildProp(self, prop):
490 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700492 if prop_val:
493 return prop_val
494
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000496 source_order_val = self._GetRawBuildProp(
497 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700498 if source_order_val:
499 source_order = source_order_val.split(",")
500 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700501 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700502
503 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700504 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700505 raise ExternalError(
506 "Invalid ro.product.property_source_order '{}'".format(source_order))
507
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700509 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000510 "ro.product", "ro.product.{}".format(source_partition), 1)
511 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700512 if prop_val:
513 return prop_val
514
515 raise ExternalError("couldn't resolve {}".format(prop))
516
Steven Laver8e2086e2020-04-27 16:26:31 -0700517 def _GetRoProductPropsDefaultSourceOrder(self):
518 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
519 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000522 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700523 if android_version == "10":
524 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
525 # NOTE: float() conversion of android_version will have rounding error.
526 # We are checking for "9" or less, and using "< 10" is well outside of
527 # possible floating point rounding.
528 try:
529 android_version_val = float(android_version)
530 except ValueError:
531 android_version_val = 0
532 if android_version_val < 10:
533 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
534 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
535
Tianjieb37c5be2020-10-15 21:27:10 -0700536 def _GetPlatformVersion(self):
537 version_sdk = self.GetBuildProp("ro.build.version.sdk")
538 # init code switches to version_release_or_codename (see b/158483506). After
539 # API finalization, release_or_codename will be the same as release. This
540 # is the best effort to support pre-S dev stage builds.
541 if int(version_sdk) >= 30:
542 try:
543 return self.GetBuildProp("ro.build.version.release_or_codename")
544 except ExternalError:
545 logger.warning('Failed to find ro.build.version.release_or_codename')
546
547 return self.GetBuildProp("ro.build.version.release")
548
549 def _GetPartitionPlatformVersion(self, partition):
550 try:
551 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
552 partition)
553 except ExternalError:
554 return self.GetPartitionBuildProp("ro.build.version.release",
555 partition)
556
Tao Bao1c320f82019-10-04 23:25:12 -0700557 def GetOemProperty(self, key):
558 if self.oem_props is not None and key in self.oem_props:
559 return self.oem_dicts[0][key]
560 return self.GetBuildProp(key)
561
Daniel Normand5fe8622020-01-08 17:01:11 -0800562 def GetPartitionFingerprint(self, partition):
563 return self._partition_fingerprints.get(partition, None)
564
565 def CalculatePartitionFingerprint(self, partition):
566 try:
567 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
568 except ExternalError:
569 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
570 self.GetPartitionBuildProp("ro.product.brand", partition),
571 self.GetPartitionBuildProp("ro.product.name", partition),
572 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700573 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800574 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400575 self.GetPartitionBuildProp(
576 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800577 self.GetPartitionBuildProp("ro.build.type", partition),
578 self.GetPartitionBuildProp("ro.build.tags", partition))
579
Tao Bao1c320f82019-10-04 23:25:12 -0700580 def CalculateFingerprint(self):
581 if self.oem_props is None:
582 try:
583 return self.GetBuildProp("ro.build.fingerprint")
584 except ExternalError:
585 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
586 self.GetBuildProp("ro.product.brand"),
587 self.GetBuildProp("ro.product.name"),
588 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700589 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700590 self.GetBuildProp("ro.build.id"),
591 self.GetBuildProp("ro.build.version.incremental"),
592 self.GetBuildProp("ro.build.type"),
593 self.GetBuildProp("ro.build.tags"))
594 return "%s/%s/%s:%s" % (
595 self.GetOemProperty("ro.product.brand"),
596 self.GetOemProperty("ro.product.name"),
597 self.GetOemProperty("ro.product.device"),
598 self.GetBuildProp("ro.build.thumbprint"))
599
600 def WriteMountOemScript(self, script):
601 assert self.oem_props is not None
602 recovery_mount_options = self.info_dict.get("recovery_mount_options")
603 script.Mount("/oem", recovery_mount_options)
604
605 def WriteDeviceAssertions(self, script, oem_no_mount):
606 # Read the property directly if not using OEM properties.
607 if not self.oem_props:
608 script.AssertDevice(self.device)
609 return
610
611 # Otherwise assert OEM properties.
612 if not self.oem_dicts:
613 raise ExternalError(
614 "No OEM file provided to answer expected assertions")
615
616 for prop in self.oem_props.split():
617 values = []
618 for oem_dict in self.oem_dicts:
619 if prop in oem_dict:
620 values.append(oem_dict[prop])
621 if not values:
622 raise ExternalError(
623 "The OEM file is missing the property %s" % (prop,))
624 script.AssertOemProperty(prop, values, oem_no_mount)
625
626
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000627def ReadFromInputFile(input_file, fn):
628 """Reads the contents of fn from input zipfile or directory."""
629 if isinstance(input_file, zipfile.ZipFile):
630 return input_file.read(fn).decode()
631 else:
632 path = os.path.join(input_file, *fn.split("/"))
633 try:
634 with open(path) as f:
635 return f.read()
636 except IOError as e:
637 if e.errno == errno.ENOENT:
638 raise KeyError(fn)
639
640
Yifan Hong10482a22021-01-07 14:38:41 -0800641def ExtractFromInputFile(input_file, fn):
642 """Extracts the contents of fn from input zipfile or directory into a file."""
643 if isinstance(input_file, zipfile.ZipFile):
644 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500645 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800646 f.write(input_file.read(fn))
647 return tmp_file
648 else:
649 file = os.path.join(input_file, *fn.split("/"))
650 if not os.path.exists(file):
651 raise KeyError(fn)
652 return file
653
jiajia tangf3f842b2021-03-17 21:49:44 +0800654class RamdiskFormat(object):
655 LZ4 = 1
656 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800657
jiajia tang836f76b2021-04-02 14:48:26 +0800658def _GetRamdiskFormat(info_dict):
659 if info_dict.get('lz4_ramdisks') == 'true':
660 ramdisk_format = RamdiskFormat.LZ4
661 else:
662 ramdisk_format = RamdiskFormat.GZ
663 return ramdisk_format
664
Tao Bao410ad8b2018-08-24 12:08:38 -0700665def LoadInfoDict(input_file, repacking=False):
666 """Loads the key/value pairs from the given input target_files.
667
Tianjiea85bdf02020-07-29 11:56:19 -0700668 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700669 checks and returns the parsed key/value pairs for to the given build. It's
670 usually called early when working on input target_files files, e.g. when
671 generating OTAs, or signing builds. Note that the function may be called
672 against an old target_files file (i.e. from past dessert releases). So the
673 property parsing needs to be backward compatible.
674
675 In a `META/misc_info.txt`, a few properties are stored as links to the files
676 in the PRODUCT_OUT directory. It works fine with the build system. However,
677 they are no longer available when (re)generating images from target_files zip.
678 When `repacking` is True, redirect these properties to the actual files in the
679 unzipped directory.
680
681 Args:
682 input_file: The input target_files file, which could be an open
683 zipfile.ZipFile instance, or a str for the dir that contains the files
684 unzipped from a target_files file.
685 repacking: Whether it's trying repack an target_files file after loading the
686 info dict (default: False). If so, it will rewrite a few loaded
687 properties (e.g. selinux_fc, root_dir) to point to the actual files in
688 target_files file. When doing repacking, `input_file` must be a dir.
689
690 Returns:
691 A dict that contains the parsed key/value pairs.
692
693 Raises:
694 AssertionError: On invalid input arguments.
695 ValueError: On malformed input values.
696 """
697 if repacking:
698 assert isinstance(input_file, str), \
699 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700700
Doug Zongkerc9253822014-02-04 12:17:58 -0800701 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000702 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800703
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700704 try:
Michael Runge6e836112014-04-15 17:40:21 -0700705 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700706 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700707 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700708
Tao Bao410ad8b2018-08-24 12:08:38 -0700709 if "recovery_api_version" not in d:
710 raise ValueError("Failed to find 'recovery_api_version'")
711 if "fstab_version" not in d:
712 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800713
Tao Bao410ad8b2018-08-24 12:08:38 -0700714 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700715 # "selinux_fc" properties should point to the file_contexts files
716 # (file_contexts.bin) under META/.
717 for key in d:
718 if key.endswith("selinux_fc"):
719 fc_basename = os.path.basename(d[key])
720 fc_config = os.path.join(input_file, "META", fc_basename)
721 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700722
Daniel Norman72c626f2019-05-13 15:58:14 -0700723 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700724
Tom Cherryd14b8952018-08-09 14:26:00 -0700725 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700726 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700727 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700728 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700729
David Anderson0ec64ac2019-12-06 12:21:18 -0800730 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700731 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700732 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800733 key_name = part_name + "_base_fs_file"
734 if key_name not in d:
735 continue
736 basename = os.path.basename(d[key_name])
737 base_fs_file = os.path.join(input_file, "META", basename)
738 if os.path.exists(base_fs_file):
739 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700740 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700741 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800742 "Failed to find %s base fs file: %s", part_name, base_fs_file)
743 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700744
Doug Zongker37974732010-09-16 17:44:38 -0700745 def makeint(key):
746 if key in d:
747 d[key] = int(d[key], 0)
748
749 makeint("recovery_api_version")
750 makeint("blocksize")
751 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700752 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700753 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700754 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700755 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800756 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700757
Steve Muckle903a1ca2020-05-07 17:32:10 -0700758 boot_images = "boot.img"
759 if "boot_images" in d:
760 boot_images = d["boot_images"]
761 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400762 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700763
Tao Bao765668f2019-10-04 22:03:00 -0700764 # Load recovery fstab if applicable.
765 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800766 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800767
Tianjie Xu861f4132018-09-12 11:49:33 -0700768 # Tries to load the build props for all partitions with care_map, including
769 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800770 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800771 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000772 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800773 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700774 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800775
Tao Bao3ed35d32019-10-07 20:48:48 -0700776 # Set up the salt (based on fingerprint) that will be used when adding AVB
777 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800778 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700779 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800780 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800781 fingerprint = build_info.GetPartitionFingerprint(partition)
782 if fingerprint:
Daniel Norman2d7989a2021-04-05 17:40:47 +0000783 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400784 try:
785 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
786 except KeyError:
787 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700788 return d
789
Tao Baod1de6f32017-03-01 16:38:48 -0800790
Daniel Norman2d7989a2021-04-05 17:40:47 +0000791
Daniel Norman4cc9df62019-07-18 10:11:07 -0700792def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900793 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700794 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900795
Daniel Norman4cc9df62019-07-18 10:11:07 -0700796
797def LoadDictionaryFromFile(file_path):
798 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900799 return LoadDictionaryFromLines(lines)
800
801
Michael Runge6e836112014-04-15 17:40:21 -0700802def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700803 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700804 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700805 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700806 if not line or line.startswith("#"):
807 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700808 if "=" in line:
809 name, value = line.split("=", 1)
810 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700811 return d
812
Tao Baod1de6f32017-03-01 16:38:48 -0800813
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000814class PartitionBuildProps(object):
815 """The class holds the build prop of a particular partition.
816
817 This class loads the build.prop and holds the build properties for a given
818 partition. It also partially recognizes the 'import' statement in the
819 build.prop; and calculates alternative values of some specific build
820 properties during runtime.
821
822 Attributes:
823 input_file: a zipped target-file or an unzipped target-file directory.
824 partition: name of the partition.
825 props_allow_override: a list of build properties to search for the
826 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 build_props: a dict of build properties for the given partition.
828 prop_overrides: a set of props that are overridden by import.
829 placeholder_values: A dict of runtime variables' values to replace the
830 placeholders in the build.prop file. We expect exactly one value for
831 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800832 ramdisk_format: If name is "boot", the format of ramdisk inside the
833 boot image. Otherwise, its value is ignored.
834 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000835 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400836
Tianjie Xu9afb2212020-05-10 21:48:15 +0000837 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000838 self.input_file = input_file
839 self.partition = name
840 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000843 self.prop_overrides = set()
844 self.placeholder_values = {}
845 if placeholder_values:
846 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000847
848 @staticmethod
849 def FromDictionary(name, build_props):
850 """Constructs an instance from a build prop dictionary."""
851
852 props = PartitionBuildProps("unknown", name)
853 props.build_props = build_props.copy()
854 return props
855
856 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800857 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000858 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800859
860 if name == "boot":
jiajia tangf3f842b2021-03-17 21:49:44 +0800861 data = PartitionBuildProps._ReadBootPropFile(input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800862 else:
863 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
864
865 props = PartitionBuildProps(input_file, name, placeholder_values)
866 props._LoadBuildProp(data)
867 return props
868
869 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800870 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800871 """
872 Read build.prop for boot image from input_file.
873 Return empty string if not found.
874 """
875 try:
876 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
877 except KeyError:
878 logger.warning('Failed to read IMAGES/boot.img')
879 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800880 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800881 if prop_file is None:
882 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500883 with open(prop_file, "r") as f:
884 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800885
886 @staticmethod
887 def _ReadPartitionPropFile(input_file, name):
888 """
889 Read build.prop for name from input_file.
890 Return empty string if not found.
891 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000892 data = ''
893 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
894 '{}/build.prop'.format(name.upper())]:
895 try:
896 data = ReadFromInputFile(input_file, prop_file)
897 break
898 except KeyError:
899 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800900 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000901
Yifan Hong125d0b62020-09-24 17:07:03 -0700902 @staticmethod
903 def FromBuildPropFile(name, build_prop_file):
904 """Constructs an instance from a build prop file."""
905
906 props = PartitionBuildProps("unknown", name)
907 with open(build_prop_file) as f:
908 props._LoadBuildProp(f.read())
909 return props
910
Tianjie Xu9afb2212020-05-10 21:48:15 +0000911 def _LoadBuildProp(self, data):
912 for line in data.split('\n'):
913 line = line.strip()
914 if not line or line.startswith("#"):
915 continue
916 if line.startswith("import"):
917 overrides = self._ImportParser(line)
918 duplicates = self.prop_overrides.intersection(overrides.keys())
919 if duplicates:
920 raise ValueError('prop {} is overridden multiple times'.format(
921 ','.join(duplicates)))
922 self.prop_overrides = self.prop_overrides.union(overrides.keys())
923 self.build_props.update(overrides)
924 elif "=" in line:
925 name, value = line.split("=", 1)
926 if name in self.prop_overrides:
927 raise ValueError('prop {} is set again after overridden by import '
928 'statement'.format(name))
929 self.build_props[name] = value
930
931 def _ImportParser(self, line):
932 """Parses the build prop in a given import statement."""
933
934 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400935 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000936 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700937
938 if len(tokens) == 3:
939 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
940 return {}
941
Tianjie Xu9afb2212020-05-10 21:48:15 +0000942 import_path = tokens[1]
943 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
944 raise ValueError('Unrecognized import path {}'.format(line))
945
946 # We only recognize a subset of import statement that the init process
947 # supports. And we can loose the restriction based on how the dynamic
948 # fingerprint is used in practice. The placeholder format should be
949 # ${placeholder}, and its value should be provided by the caller through
950 # the placeholder_values.
951 for prop, value in self.placeholder_values.items():
952 prop_place_holder = '${{{}}}'.format(prop)
953 if prop_place_holder in import_path:
954 import_path = import_path.replace(prop_place_holder, value)
955 if '$' in import_path:
956 logger.info('Unresolved place holder in import path %s', import_path)
957 return {}
958
959 import_path = import_path.replace('/{}'.format(self.partition),
960 self.partition.upper())
961 logger.info('Parsing build props override from %s', import_path)
962
963 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
964 d = LoadDictionaryFromLines(lines)
965 return {key: val for key, val in d.items()
966 if key in self.props_allow_override}
967
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000968 def GetProp(self, prop):
969 return self.build_props.get(prop)
970
971
Tianjie Xucfa86222016-03-07 16:31:19 -0800972def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
973 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700974 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700975 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700976 self.mount_point = mount_point
977 self.fs_type = fs_type
978 self.device = device
979 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700980 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700981 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700982
983 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800984 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700985 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700986 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700987 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700988
Tao Baod1de6f32017-03-01 16:38:48 -0800989 assert fstab_version == 2
990
991 d = {}
992 for line in data.split("\n"):
993 line = line.strip()
994 if not line or line.startswith("#"):
995 continue
996
997 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
998 pieces = line.split()
999 if len(pieces) != 5:
1000 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1001
1002 # Ignore entries that are managed by vold.
1003 options = pieces[4]
1004 if "voldmanaged=" in options:
1005 continue
1006
1007 # It's a good line, parse it.
1008 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001009 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001010 options = options.split(",")
1011 for i in options:
1012 if i.startswith("length="):
1013 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001014 elif i == "slotselect":
1015 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001016 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001017 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001018 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001019
Tao Baod1de6f32017-03-01 16:38:48 -08001020 mount_flags = pieces[3]
1021 # Honor the SELinux context if present.
1022 context = None
1023 for i in mount_flags.split(","):
1024 if i.startswith("context="):
1025 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001026
Tao Baod1de6f32017-03-01 16:38:48 -08001027 mount_point = pieces[1]
1028 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001029 device=pieces[0], length=length, context=context,
1030 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001031
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001032 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001033 # system. Other areas assume system is always at "/system" so point /system
1034 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001035 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001036 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001037 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001038 return d
1039
1040
Tao Bao765668f2019-10-04 22:03:00 -07001041def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1042 """Finds the path to recovery fstab and loads its contents."""
1043 # recovery fstab is only meaningful when installing an update via recovery
1044 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001045 if info_dict.get('ab_update') == 'true' and \
1046 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001047 return None
1048
1049 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1050 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1051 # cases, since it may load the info_dict from an old build (e.g. when
1052 # generating incremental OTAs from that build).
1053 system_root_image = info_dict.get('system_root_image') == 'true'
1054 if info_dict.get('no_recovery') != 'true':
1055 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1056 if isinstance(input_file, zipfile.ZipFile):
1057 if recovery_fstab_path not in input_file.namelist():
1058 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1059 else:
1060 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1061 if not os.path.exists(path):
1062 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1063 return LoadRecoveryFSTab(
1064 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1065 system_root_image)
1066
1067 if info_dict.get('recovery_as_boot') == 'true':
1068 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1069 if isinstance(input_file, zipfile.ZipFile):
1070 if recovery_fstab_path not in input_file.namelist():
1071 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1072 else:
1073 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1074 if not os.path.exists(path):
1075 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1076 return LoadRecoveryFSTab(
1077 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1078 system_root_image)
1079
1080 return None
1081
1082
Doug Zongker37974732010-09-16 17:44:38 -07001083def DumpInfoDict(d):
1084 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001085 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001086
Dan Albert8b72aef2015-03-23 19:13:21 -07001087
Daniel Norman55417142019-11-25 16:04:36 -08001088def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001089 """Merges dynamic partition info variables.
1090
1091 Args:
1092 framework_dict: The dictionary of dynamic partition info variables from the
1093 partial framework target files.
1094 vendor_dict: The dictionary of dynamic partition info variables from the
1095 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001096
1097 Returns:
1098 The merged dynamic partition info dictionary.
1099 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001100
1101 def uniq_concat(a, b):
1102 combined = set(a.split(" "))
1103 combined.update(set(b.split(" ")))
1104 combined = [item.strip() for item in combined if item.strip()]
1105 return " ".join(sorted(combined))
1106
1107 if (framework_dict.get("use_dynamic_partitions") !=
Daniel Norman2d7989a2021-04-05 17:40:47 +00001108 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001109 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1110
1111 merged_dict = {"use_dynamic_partitions": "true"}
1112
1113 merged_dict["dynamic_partition_list"] = uniq_concat(
1114 framework_dict.get("dynamic_partition_list", ""),
1115 vendor_dict.get("dynamic_partition_list", ""))
1116
1117 # Super block devices are defined by the vendor dict.
1118 if "super_block_devices" in vendor_dict:
1119 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1120 for block_device in merged_dict["super_block_devices"].split(" "):
1121 key = "super_%s_device_size" % block_device
1122 if key not in vendor_dict:
1123 raise ValueError("Vendor dict does not contain required key %s." % key)
1124 merged_dict[key] = vendor_dict[key]
1125
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001126 # Partition groups and group sizes are defined by the vendor dict because
1127 # these values may vary for each board that uses a shared system image.
1128 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001129 for partition_group in merged_dict["super_partition_groups"].split(" "):
1130 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001131 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001132 if key not in vendor_dict:
1133 raise ValueError("Vendor dict does not contain required key %s." % key)
1134 merged_dict[key] = vendor_dict[key]
1135
1136 # Set the partition group's partition list using a concatenation of the
1137 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001138 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001139 merged_dict[key] = uniq_concat(
1140 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301141
Daniel Normanb0c75912020-09-24 14:30:21 -07001142 # Various other flags should be copied from the vendor dict, if defined.
1143 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1144 "super_metadata_device", "super_partition_error_limit",
1145 "super_partition_size"):
1146 if key in vendor_dict.keys():
1147 merged_dict[key] = vendor_dict[key]
1148
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001149 return merged_dict
1150
1151
Daniel Norman21c34f72020-11-11 17:25:50 -08001152def PartitionMapFromTargetFiles(target_files_dir):
1153 """Builds a map from partition -> path within an extracted target files directory."""
1154 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1155 possible_subdirs = {
1156 "system": ["SYSTEM"],
1157 "vendor": ["VENDOR", "SYSTEM/vendor"],
1158 "product": ["PRODUCT", "SYSTEM/product"],
1159 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1160 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1161 "vendor_dlkm": [
1162 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1163 ],
1164 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1165 }
1166 partition_map = {}
1167 for partition, subdirs in possible_subdirs.items():
1168 for subdir in subdirs:
1169 if os.path.exists(os.path.join(target_files_dir, subdir)):
1170 partition_map[partition] = subdir
1171 break
1172 return partition_map
1173
1174
Daniel Normand3351562020-10-29 12:33:11 -07001175def SharedUidPartitionViolations(uid_dict, partition_groups):
1176 """Checks for APK sharedUserIds that cross partition group boundaries.
1177
1178 This uses a single or merged build's shareduid_violation_modules.json
1179 output file, as generated by find_shareduid_violation.py or
1180 core/tasks/find-shareduid-violation.mk.
1181
1182 An error is defined as a sharedUserId that is found in a set of partitions
1183 that span more than one partition group.
1184
1185 Args:
1186 uid_dict: A dictionary created by using the standard json module to read a
1187 complete shareduid_violation_modules.json file.
1188 partition_groups: A list of groups, where each group is a list of
1189 partitions.
1190
1191 Returns:
1192 A list of error messages.
1193 """
1194 errors = []
1195 for uid, partitions in uid_dict.items():
1196 found_in_groups = [
1197 group for group in partition_groups
1198 if set(partitions.keys()) & set(group)
1199 ]
1200 if len(found_in_groups) > 1:
1201 errors.append(
1202 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1203 % (uid, ",".join(sorted(partitions.keys()))))
1204 return errors
1205
1206
Daniel Norman21c34f72020-11-11 17:25:50 -08001207def RunHostInitVerifier(product_out, partition_map):
1208 """Runs host_init_verifier on the init rc files within partitions.
1209
1210 host_init_verifier searches the etc/init path within each partition.
1211
1212 Args:
1213 product_out: PRODUCT_OUT directory, containing partition directories.
1214 partition_map: A map of partition name -> relative path within product_out.
1215 """
1216 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1217 cmd = ["host_init_verifier"]
1218 for partition, path in partition_map.items():
1219 if partition not in allowed_partitions:
1220 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1221 partition)
1222 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1223 # Add --property-contexts if the file exists on the partition.
1224 property_contexts = "%s_property_contexts" % (
1225 "plat" if partition == "system" else partition)
1226 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1227 property_contexts)
1228 if os.path.exists(property_contexts_path):
1229 cmd.append("--property-contexts=%s" % property_contexts_path)
1230 # Add the passwd file if the file exists on the partition.
1231 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1232 if os.path.exists(passwd_path):
1233 cmd.extend(["-p", passwd_path])
1234 return RunAndCheckOutput(cmd)
1235
1236
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001237def AppendAVBSigningArgs(cmd, partition):
1238 """Append signing arguments for avbtool."""
1239 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1240 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001241 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1242 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1243 if os.path.exists(new_key_path):
1244 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001245 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1246 if key_path and algorithm:
1247 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001248 avb_salt = OPTIONS.info_dict.get("avb_salt")
1249 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001250 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001251 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001252
1253
Tao Bao765668f2019-10-04 22:03:00 -07001254def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001255 """Returns the VBMeta arguments for partition.
1256
1257 It sets up the VBMeta argument by including the partition descriptor from the
1258 given 'image', or by configuring the partition as a chained partition.
1259
1260 Args:
1261 partition: The name of the partition (e.g. "system").
1262 image: The path to the partition image.
1263 info_dict: A dict returned by common.LoadInfoDict(). Will use
1264 OPTIONS.info_dict if None has been given.
1265
1266 Returns:
1267 A list of VBMeta arguments.
1268 """
1269 if info_dict is None:
1270 info_dict = OPTIONS.info_dict
1271
1272 # Check if chain partition is used.
1273 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001274 if not key_path:
1275 return ["--include_descriptors_from_image", image]
1276
1277 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1278 # into vbmeta.img. The recovery image will be configured on an independent
1279 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1280 # See details at
1281 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001282 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001283 return []
1284
1285 # Otherwise chain the partition into vbmeta.
1286 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1287 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001288
1289
Tao Bao02a08592018-07-22 12:40:45 -07001290def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1291 """Constructs and returns the arg to build or verify a chained partition.
1292
1293 Args:
1294 partition: The partition name.
1295 info_dict: The info dict to look up the key info and rollback index
1296 location.
1297 key: The key to be used for building or verifying the partition. Defaults to
1298 the key listed in info_dict.
1299
1300 Returns:
1301 A string of form "partition:rollback_index_location:key" that can be used to
1302 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001303 """
1304 if key is None:
1305 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001306 if key and not os.path.exists(key) and OPTIONS.search_path:
1307 new_key_path = os.path.join(OPTIONS.search_path, key)
1308 if os.path.exists(new_key_path):
1309 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001310 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001311 rollback_index_location = info_dict[
1312 "avb_" + partition + "_rollback_index_location"]
1313 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1314
1315
Tianjie20dd8f22020-04-19 15:51:16 -07001316def ConstructAftlMakeImageCommands(output_image):
1317 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001318
1319 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001320 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001321 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1322 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1323 'No AFTL manufacturer key provided.'
1324
1325 vbmeta_image = MakeTempFile()
1326 os.rename(output_image, vbmeta_image)
1327 build_info = BuildInfo(OPTIONS.info_dict)
1328 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001329 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001330 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001331 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 "--vbmeta_image_path", vbmeta_image,
1333 "--output", output_image,
1334 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001335 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001336 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1337 "--algorithm", "SHA256_RSA4096",
1338 "--padding", "4096"]
1339 if OPTIONS.aftl_signer_helper:
1340 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001341 return aftl_cmd
1342
1343
1344def AddAftlInclusionProof(output_image):
1345 """Appends the aftl inclusion proof to the vbmeta image."""
1346
1347 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001348 RunAndCheckOutput(aftl_cmd)
1349
1350 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1351 output_image, '--transparency_log_pub_keys',
1352 OPTIONS.aftl_key_path]
1353 RunAndCheckOutput(verify_cmd)
1354
1355
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001356def AppendGkiSigningArgs(cmd):
1357 """Append GKI signing arguments for mkbootimg."""
1358 # e.g., --gki_signing_key path/to/signing_key
1359 # --gki_signing_algorithm SHA256_RSA4096"
1360
1361 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1362 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1363 if not key_path:
1364 return
1365
1366 if not os.path.exists(key_path) and OPTIONS.search_path:
1367 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1368 if os.path.exists(new_key_path):
1369 key_path = new_key_path
1370
1371 # Checks key_path exists, before appending --gki_signing_* args.
1372 if not os.path.exists(key_path):
1373 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1374
1375 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1376 if key_path and algorithm:
1377 cmd.extend(["--gki_signing_key", key_path,
1378 "--gki_signing_algorithm", algorithm])
1379
1380 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1381 if signature_args:
1382 cmd.extend(["--gki_signing_signature_args", signature_args])
1383
1384
Daniel Norman276f0622019-07-26 14:13:51 -07001385def BuildVBMeta(image_path, partitions, name, needed_partitions):
1386 """Creates a VBMeta image.
1387
1388 It generates the requested VBMeta image. The requested image could be for
1389 top-level or chained VBMeta image, which is determined based on the name.
1390
1391 Args:
1392 image_path: The output path for the new VBMeta image.
1393 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001394 values. Only valid partition names are accepted, as partitions listed
1395 in common.AVB_PARTITIONS and custom partitions listed in
1396 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001397 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1398 needed_partitions: Partitions whose descriptors should be included into the
1399 generated VBMeta image.
1400
1401 Raises:
1402 AssertionError: On invalid input args.
1403 """
1404 avbtool = OPTIONS.info_dict["avb_avbtool"]
1405 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1406 AppendAVBSigningArgs(cmd, name)
1407
Hongguang Chenf23364d2020-04-27 18:36:36 -07001408 custom_partitions = OPTIONS.info_dict.get(
1409 "avb_custom_images_partition_list", "").strip().split()
1410
Daniel Norman276f0622019-07-26 14:13:51 -07001411 for partition, path in partitions.items():
1412 if partition not in needed_partitions:
1413 continue
1414 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001415 partition in AVB_VBMETA_PARTITIONS or
1416 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001417 'Unknown partition: {}'.format(partition)
1418 assert os.path.exists(path), \
1419 'Failed to find {} for {}'.format(path, partition)
1420 cmd.extend(GetAvbPartitionArg(partition, path))
1421
1422 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1423 if args and args.strip():
1424 split_args = shlex.split(args)
1425 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001426 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001427 # as a path relative to source tree, which may not be available at the
1428 # same location when running this script (we have the input target_files
1429 # zip only). For such cases, we additionally scan other locations (e.g.
1430 # IMAGES/, RADIO/, etc) before bailing out.
1431 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001432 chained_image = split_args[index + 1]
1433 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001434 continue
1435 found = False
1436 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1437 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001438 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001439 if os.path.exists(alt_path):
1440 split_args[index + 1] = alt_path
1441 found = True
1442 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001443 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001444 cmd.extend(split_args)
1445
1446 RunAndCheckOutput(cmd)
1447
Tianjie Xueaed60c2020-03-12 00:33:28 -07001448 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001449 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001450 AddAftlInclusionProof(image_path)
1451
Daniel Norman276f0622019-07-26 14:13:51 -07001452
jiajia tang836f76b2021-04-02 14:48:26 +08001453def _MakeRamdisk(sourcedir, fs_config_file=None,
1454 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001455 ramdisk_img = tempfile.NamedTemporaryFile()
1456
1457 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1458 cmd = ["mkbootfs", "-f", fs_config_file,
1459 os.path.join(sourcedir, "RAMDISK")]
1460 else:
1461 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1462 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001463 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001464 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001465 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001466 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001467 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001468 else:
1469 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001470
1471 p2.wait()
1472 p1.wait()
1473 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001474 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001475
1476 return ramdisk_img
1477
1478
Steve Muckle9793cf62020-04-08 18:27:00 -07001479def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001480 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001481 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001482
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001483 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001484 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1485 we are building a two-step special image (i.e. building a recovery image to
1486 be loaded into /boot in two-step OTAs).
1487
1488 Return the image data, or None if sourcedir does not appear to contains files
1489 for building the requested image.
1490 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001491
Yifan Hong63c5ca12020-10-08 11:54:02 -07001492 if info_dict is None:
1493 info_dict = OPTIONS.info_dict
1494
Steve Muckle9793cf62020-04-08 18:27:00 -07001495 # "boot" or "recovery", without extension.
1496 partition_name = os.path.basename(sourcedir).lower()
1497
Yifan Hong63c5ca12020-10-08 11:54:02 -07001498 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001499 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001500 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1501 logger.info("Excluded kernel binary from recovery image.")
1502 else:
1503 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001504 else:
1505 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001506 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001507 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001508 return None
1509
1510 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001511 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001512
Doug Zongkereef39442009-04-02 12:14:19 -07001513 img = tempfile.NamedTemporaryFile()
1514
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001515 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001516 ramdisk_format = _GetRamdiskFormat(info_dict)
1517 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1518 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001519
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001520 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1521 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1522
Yifan Hong63c5ca12020-10-08 11:54:02 -07001523 cmd = [mkbootimg]
1524 if kernel:
1525 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001526
Benoit Fradina45a8682014-07-14 21:00:43 +02001527 fn = os.path.join(sourcedir, "second")
1528 if os.access(fn, os.F_OK):
1529 cmd.append("--second")
1530 cmd.append(fn)
1531
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001532 fn = os.path.join(sourcedir, "dtb")
1533 if os.access(fn, os.F_OK):
1534 cmd.append("--dtb")
1535 cmd.append(fn)
1536
Doug Zongker171f1cd2009-06-15 22:36:37 -07001537 fn = os.path.join(sourcedir, "cmdline")
1538 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001539 cmd.append("--cmdline")
1540 cmd.append(open(fn).read().rstrip("\n"))
1541
1542 fn = os.path.join(sourcedir, "base")
1543 if os.access(fn, os.F_OK):
1544 cmd.append("--base")
1545 cmd.append(open(fn).read().rstrip("\n"))
1546
Ying Wang4de6b5b2010-08-25 14:29:34 -07001547 fn = os.path.join(sourcedir, "pagesize")
1548 if os.access(fn, os.F_OK):
1549 cmd.append("--pagesize")
1550 cmd.append(open(fn).read().rstrip("\n"))
1551
Steve Mucklef84668e2020-03-16 19:13:46 -07001552 if partition_name == "recovery":
1553 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301554 if not args:
1555 # Fall back to "mkbootimg_args" for recovery image
1556 # in case "recovery_mkbootimg_args" is not set.
1557 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001558 else:
1559 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001560 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001561 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001562
Tao Bao76def242017-11-21 09:25:31 -08001563 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001564 if args and args.strip():
1565 cmd.extend(shlex.split(args))
1566
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001567 if has_ramdisk:
1568 cmd.extend(["--ramdisk", ramdisk_img.name])
1569
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001570 AppendGkiSigningArgs(cmd)
1571
Tao Baod95e9fd2015-03-29 23:07:41 -07001572 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001573 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001574 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001575 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001576 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001577 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001578
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001579 if partition_name == "recovery":
1580 if info_dict.get("include_recovery_dtbo") == "true":
1581 fn = os.path.join(sourcedir, "recovery_dtbo")
1582 cmd.extend(["--recovery_dtbo", fn])
1583 if info_dict.get("include_recovery_acpio") == "true":
1584 fn = os.path.join(sourcedir, "recovery_acpio")
1585 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001586
Tao Bao986ee862018-10-04 15:46:16 -07001587 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001588
Tao Bao76def242017-11-21 09:25:31 -08001589 if (info_dict.get("boot_signer") == "true" and
Daniel Norman2d7989a2021-04-05 17:40:47 +00001590 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001591 # Hard-code the path as "/boot" for two-step special recovery image (which
1592 # will be loaded into /boot during the two-step OTA).
1593 if two_step_image:
1594 path = "/boot"
1595 else:
Tao Baobf70c312017-07-11 17:27:55 -07001596 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001597 cmd = [OPTIONS.boot_signer_path]
1598 cmd.extend(OPTIONS.boot_signer_args)
1599 cmd.extend([path, img.name,
1600 info_dict["verity_key"] + ".pk8",
1601 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001602 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001603
Tao Baod95e9fd2015-03-29 23:07:41 -07001604 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001605 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001606 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001607 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001608 # We have switched from the prebuilt futility binary to using the tool
1609 # (futility-host) built from the source. Override the setting in the old
1610 # TF.zip.
1611 futility = info_dict["futility"]
1612 if futility.startswith("prebuilts/"):
1613 futility = "futility-host"
1614 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001615 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001616 info_dict["vboot_key"] + ".vbprivk",
1617 info_dict["vboot_subkey"] + ".vbprivk",
1618 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001619 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001620 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001621
Tao Baof3282b42015-04-01 11:21:55 -07001622 # Clean up the temp files.
1623 img_unsigned.close()
1624 img_keyblock.close()
1625
David Zeuthen8fecb282017-12-01 16:24:01 -05001626 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001627 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001628 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001629 if partition_name == "recovery":
1630 part_size = info_dict["recovery_size"]
1631 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001632 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001633 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001634 "--partition_size", str(part_size), "--partition_name",
1635 partition_name]
1636 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001637 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001638 if args and args.strip():
1639 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001640 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001641
1642 img.seek(os.SEEK_SET, 0)
1643 data = img.read()
1644
1645 if has_ramdisk:
1646 ramdisk_img.close()
1647 img.close()
1648
1649 return data
1650
1651
Doug Zongkerd5131602012-08-02 14:46:42 -07001652def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001653 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001654 """Return a File object with the desired bootable image.
1655
1656 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1657 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1658 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001659
Doug Zongker55d93282011-01-25 17:03:34 -08001660 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1661 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001662 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001663 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001664
1665 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1666 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001667 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001668 return File.FromLocalFile(name, prebuilt_path)
1669
Tao Bao32fcdab2018-10-12 10:30:39 -07001670 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001671
1672 if info_dict is None:
1673 info_dict = OPTIONS.info_dict
1674
1675 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001676 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1677 # for recovery.
1678 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1679 prebuilt_name != "boot.img" or
1680 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001681
Doug Zongker6f1d0312014-08-22 08:07:12 -07001682 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001683 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001684 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001685 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001686 if data:
1687 return File(name, data)
1688 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001689
Doug Zongkereef39442009-04-02 12:14:19 -07001690
Steve Mucklee1b10862019-07-10 10:49:37 -07001691def _BuildVendorBootImage(sourcedir, info_dict=None):
1692 """Build a vendor boot image from the specified sourcedir.
1693
1694 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1695 turn them into a vendor boot image.
1696
1697 Return the image data, or None if sourcedir does not appear to contains files
1698 for building the requested image.
1699 """
1700
1701 if info_dict is None:
1702 info_dict = OPTIONS.info_dict
1703
1704 img = tempfile.NamedTemporaryFile()
1705
jiajia tang836f76b2021-04-02 14:48:26 +08001706 ramdisk_format = _GetRamdiskFormat(info_dict)
1707 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001708
1709 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1710 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1711
1712 cmd = [mkbootimg]
1713
1714 fn = os.path.join(sourcedir, "dtb")
1715 if os.access(fn, os.F_OK):
1716 cmd.append("--dtb")
1717 cmd.append(fn)
1718
1719 fn = os.path.join(sourcedir, "vendor_cmdline")
1720 if os.access(fn, os.F_OK):
1721 cmd.append("--vendor_cmdline")
1722 cmd.append(open(fn).read().rstrip("\n"))
1723
1724 fn = os.path.join(sourcedir, "base")
1725 if os.access(fn, os.F_OK):
1726 cmd.append("--base")
1727 cmd.append(open(fn).read().rstrip("\n"))
1728
1729 fn = os.path.join(sourcedir, "pagesize")
1730 if os.access(fn, os.F_OK):
1731 cmd.append("--pagesize")
1732 cmd.append(open(fn).read().rstrip("\n"))
1733
1734 args = info_dict.get("mkbootimg_args")
1735 if args and args.strip():
1736 cmd.extend(shlex.split(args))
1737
1738 args = info_dict.get("mkbootimg_version_args")
1739 if args and args.strip():
1740 cmd.extend(shlex.split(args))
1741
1742 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1743 cmd.extend(["--vendor_boot", img.name])
1744
Devin Moore50509012021-01-13 10:45:04 -08001745 fn = os.path.join(sourcedir, "vendor_bootconfig")
1746 if os.access(fn, os.F_OK):
1747 cmd.append("--vendor_bootconfig")
1748 cmd.append(fn)
1749
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001750 ramdisk_fragment_imgs = []
1751 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1752 if os.access(fn, os.F_OK):
1753 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1754 for ramdisk_fragment in ramdisk_fragments:
Daniel Norman2d7989a2021-04-05 17:40:47 +00001755 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001756 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Daniel Norman2d7989a2021-04-05 17:40:47 +00001757 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001758 # Use prebuilt image if found, else create ramdisk from supplied files.
1759 if os.access(fn, os.F_OK):
1760 ramdisk_fragment_pathname = fn
1761 else:
Daniel Norman2d7989a2021-04-05 17:40:47 +00001762 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001763 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1764 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001765 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1766 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1767 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1768
Steve Mucklee1b10862019-07-10 10:49:37 -07001769 RunAndCheckOutput(cmd)
1770
1771 # AVB: if enabled, calculate and add hash.
1772 if info_dict.get("avb_enable") == "true":
1773 avbtool = info_dict["avb_avbtool"]
1774 part_size = info_dict["vendor_boot_size"]
1775 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001776 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001777 AppendAVBSigningArgs(cmd, "vendor_boot")
1778 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1779 if args and args.strip():
1780 cmd.extend(shlex.split(args))
1781 RunAndCheckOutput(cmd)
1782
1783 img.seek(os.SEEK_SET, 0)
1784 data = img.read()
1785
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001786 for f in ramdisk_fragment_imgs:
1787 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001788 ramdisk_img.close()
1789 img.close()
1790
1791 return data
1792
1793
1794def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1795 info_dict=None):
1796 """Return a File object with the desired vendor boot image.
1797
1798 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1799 the source files in 'unpack_dir'/'tree_subdir'."""
1800
1801 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1802 if os.path.exists(prebuilt_path):
1803 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1804 return File.FromLocalFile(name, prebuilt_path)
1805
1806 logger.info("building image from target_files %s...", tree_subdir)
1807
1808 if info_dict is None:
1809 info_dict = OPTIONS.info_dict
1810
Kelvin Zhang0876c412020-06-23 15:06:58 -04001811 data = _BuildVendorBootImage(
1812 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001813 if data:
1814 return File(name, data)
1815 return None
1816
1817
Narayan Kamatha07bf042017-08-14 14:49:21 +01001818def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001819 """Gunzips the given gzip compressed file to a given output file."""
1820 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001821 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001822 shutil.copyfileobj(in_file, out_file)
1823
1824
Tao Bao0ff15de2019-03-20 11:26:06 -07001825def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001826 """Unzips the archive to the given directory.
1827
1828 Args:
1829 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001830 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001831 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1832 archvie. Non-matching patterns will be filtered out. If there's no match
1833 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001834 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001835 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001836 if patterns is not None:
1837 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001838 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001839 names = input_zip.namelist()
1840 filtered = [
1841 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1842
1843 # There isn't any matching files. Don't unzip anything.
1844 if not filtered:
1845 return
1846 cmd.extend(filtered)
1847
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001848 RunAndCheckOutput(cmd)
1849
1850
Doug Zongker75f17362009-12-08 13:46:44 -08001851def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001852 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001853
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001854 Args:
1855 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1856 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1857
1858 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1859 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001860
Tao Bao1c830bf2017-12-25 10:43:47 -08001861 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001862 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001863 """
Doug Zongkereef39442009-04-02 12:14:19 -07001864
Tao Bao1c830bf2017-12-25 10:43:47 -08001865 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001866 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1867 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001868 UnzipToDir(m.group(1), tmp, pattern)
1869 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001870 filename = m.group(1)
1871 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001872 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001873
Tao Baodba59ee2018-01-09 13:21:02 -08001874 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001875
1876
Yifan Hong8a66a712019-04-04 15:37:57 -07001877def GetUserImage(which, tmpdir, input_zip,
1878 info_dict=None,
1879 allow_shared_blocks=None,
1880 hashtree_info_generator=None,
1881 reset_file_map=False):
1882 """Returns an Image object suitable for passing to BlockImageDiff.
1883
1884 This function loads the specified image from the given path. If the specified
1885 image is sparse, it also performs additional processing for OTA purpose. For
1886 example, it always adds block 0 to clobbered blocks list. It also detects
1887 files that cannot be reconstructed from the block list, for whom we should
1888 avoid applying imgdiff.
1889
1890 Args:
1891 which: The partition name.
1892 tmpdir: The directory that contains the prebuilt image and block map file.
1893 input_zip: The target-files ZIP archive.
1894 info_dict: The dict to be looked up for relevant info.
1895 allow_shared_blocks: If image is sparse, whether having shared blocks is
1896 allowed. If none, it is looked up from info_dict.
1897 hashtree_info_generator: If present and image is sparse, generates the
1898 hashtree_info for this sparse image.
1899 reset_file_map: If true and image is sparse, reset file map before returning
1900 the image.
1901 Returns:
1902 A Image object. If it is a sparse image and reset_file_map is False, the
1903 image will have file_map info loaded.
1904 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001905 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001906 info_dict = LoadInfoDict(input_zip)
1907
1908 is_sparse = info_dict.get("extfs_sparse_flag")
1909
1910 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1911 # shared blocks (i.e. some blocks will show up in multiple files' block
1912 # list). We can only allocate such shared blocks to the first "owner", and
1913 # disable imgdiff for all later occurrences.
1914 if allow_shared_blocks is None:
1915 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1916
1917 if is_sparse:
1918 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1919 hashtree_info_generator)
1920 if reset_file_map:
1921 img.ResetFileMap()
1922 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001923 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001924
1925
1926def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1927 """Returns a Image object suitable for passing to BlockImageDiff.
1928
1929 This function loads the specified non-sparse image from the given path.
1930
1931 Args:
1932 which: The partition name.
1933 tmpdir: The directory that contains the prebuilt image and block map file.
1934 Returns:
1935 A Image object.
1936 """
1937 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1938 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1939
1940 # The image and map files must have been created prior to calling
1941 # ota_from_target_files.py (since LMP).
1942 assert os.path.exists(path) and os.path.exists(mappath)
1943
Tianjie Xu41976c72019-07-03 13:57:01 -07001944 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1945
Yifan Hong8a66a712019-04-04 15:37:57 -07001946
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001947def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1948 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001949 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1950
1951 This function loads the specified sparse image from the given path, and
1952 performs additional processing for OTA purpose. For example, it always adds
1953 block 0 to clobbered blocks list. It also detects files that cannot be
1954 reconstructed from the block list, for whom we should avoid applying imgdiff.
1955
1956 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001957 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001958 tmpdir: The directory that contains the prebuilt image and block map file.
1959 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001960 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001961 hashtree_info_generator: If present, generates the hashtree_info for this
1962 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001963 Returns:
1964 A SparseImage object, with file_map info loaded.
1965 """
Tao Baoc765cca2018-01-31 17:32:40 -08001966 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1967 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1968
1969 # The image and map files must have been created prior to calling
1970 # ota_from_target_files.py (since LMP).
1971 assert os.path.exists(path) and os.path.exists(mappath)
1972
1973 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1974 # it to clobbered_blocks so that it will be written to the target
1975 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1976 clobbered_blocks = "0"
1977
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001978 image = sparse_img.SparseImage(
1979 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1980 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001981
1982 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1983 # if they contain all zeros. We can't reconstruct such a file from its block
1984 # list. Tag such entries accordingly. (Bug: 65213616)
1985 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001986 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001987 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001988 continue
1989
Tom Cherryd14b8952018-08-09 14:26:00 -07001990 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1991 # filename listed in system.map may contain an additional leading slash
1992 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1993 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001994 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001995 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001996 arcname = entry.lstrip('/')
1997 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001998 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001999 else:
2000 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002001
2002 assert arcname in input_zip.namelist(), \
2003 "Failed to find the ZIP entry for {}".format(entry)
2004
Tao Baoc765cca2018-01-31 17:32:40 -08002005 info = input_zip.getinfo(arcname)
2006 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002007
2008 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002009 # image, check the original block list to determine its completeness. Note
2010 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002011 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002012 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002013
Tao Baoc765cca2018-01-31 17:32:40 -08002014 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2015 ranges.extra['incomplete'] = True
2016
2017 return image
2018
2019
Doug Zongkereef39442009-04-02 12:14:19 -07002020def GetKeyPasswords(keylist):
2021 """Given a list of keys, prompt the user to enter passwords for
2022 those which require them. Return a {key: password} dict. password
2023 will be None if the key has no password."""
2024
Doug Zongker8ce7c252009-05-22 13:34:54 -07002025 no_passwords = []
2026 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002027 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002028 devnull = open("/dev/null", "w+b")
2029 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002030 # We don't need a password for things that aren't really keys.
2031 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002032 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002033 continue
2034
T.R. Fullhart37e10522013-03-18 10:31:26 -07002035 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002036 "-inform", "DER", "-nocrypt"],
2037 stdin=devnull.fileno(),
2038 stdout=devnull.fileno(),
2039 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002040 p.communicate()
2041 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002042 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002043 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002044 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002045 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2046 "-inform", "DER", "-passin", "pass:"],
2047 stdin=devnull.fileno(),
2048 stdout=devnull.fileno(),
2049 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002050 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002051 if p.returncode == 0:
2052 # Encrypted key with empty string as password.
2053 key_passwords[k] = ''
2054 elif stderr.startswith('Error decrypting key'):
2055 # Definitely encrypted key.
2056 # It would have said "Error reading key" if it didn't parse correctly.
2057 need_passwords.append(k)
2058 else:
2059 # Potentially, a type of key that openssl doesn't understand.
2060 # We'll let the routines in signapk.jar handle it.
2061 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002062 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002063
T.R. Fullhart37e10522013-03-18 10:31:26 -07002064 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002065 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002066 return key_passwords
2067
2068
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002069def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002070 """Gets the minSdkVersion declared in the APK.
2071
changho.shin0f125362019-07-08 10:59:00 +09002072 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002073 This can be both a decimal number (API Level) or a codename.
2074
2075 Args:
2076 apk_name: The APK filename.
2077
2078 Returns:
2079 The parsed SDK version string.
2080
2081 Raises:
2082 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002083 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002084 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002085 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002086 stderr=subprocess.PIPE)
2087 stdoutdata, stderrdata = proc.communicate()
2088 if proc.returncode != 0:
2089 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002090 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002091 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002092
Tao Baof47bf0f2018-03-21 23:28:51 -07002093 for line in stdoutdata.split("\n"):
2094 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002095 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2096 if m:
2097 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002098 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002099
2100
2101def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002102 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002103
Tao Baof47bf0f2018-03-21 23:28:51 -07002104 If minSdkVersion is set to a codename, it is translated to a number using the
2105 provided map.
2106
2107 Args:
2108 apk_name: The APK filename.
2109
2110 Returns:
2111 The parsed SDK version number.
2112
2113 Raises:
2114 ExternalError: On failing to get the min SDK version number.
2115 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002116 version = GetMinSdkVersion(apk_name)
2117 try:
2118 return int(version)
2119 except ValueError:
2120 # Not a decimal number. Codename?
2121 if version in codename_to_api_level_map:
2122 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002123 raise ExternalError(
2124 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2125 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002126
2127
2128def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002129 codename_to_api_level_map=None, whole_file=False,
2130 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002131 """Sign the input_name zip/jar/apk, producing output_name. Use the
2132 given key and password (the latter may be None if the key does not
2133 have a password.
2134
Doug Zongker951495f2009-08-14 12:44:19 -07002135 If whole_file is true, use the "-w" option to SignApk to embed a
2136 signature that covers the whole file in the archive comment of the
2137 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002138
2139 min_api_level is the API Level (int) of the oldest platform this file may end
2140 up on. If not specified for an APK, the API Level is obtained by interpreting
2141 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2142
2143 codename_to_api_level_map is needed to translate the codename which may be
2144 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002145
2146 Caller may optionally specify extra args to be passed to SignApk, which
2147 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002148 """
Tao Bao76def242017-11-21 09:25:31 -08002149 if codename_to_api_level_map is None:
2150 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002151 if extra_signapk_args is None:
2152 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002153
Alex Klyubin9667b182015-12-10 13:38:50 -08002154 java_library_path = os.path.join(
2155 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2156
Tao Baoe95540e2016-11-08 12:08:53 -08002157 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2158 ["-Djava.library.path=" + java_library_path,
2159 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002160 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002161 if whole_file:
2162 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002163
2164 min_sdk_version = min_api_level
2165 if min_sdk_version is None:
2166 if not whole_file:
2167 min_sdk_version = GetMinSdkVersionInt(
2168 input_name, codename_to_api_level_map)
2169 if min_sdk_version is not None:
2170 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2171
T.R. Fullhart37e10522013-03-18 10:31:26 -07002172 cmd.extend([key + OPTIONS.public_key_suffix,
2173 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002174 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002175
Tao Bao73dd4f42018-10-04 16:25:33 -07002176 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002177 if password is not None:
2178 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002179 stdoutdata, _ = proc.communicate(password)
2180 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002181 raise ExternalError(
2182 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002183 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002184
Doug Zongkereef39442009-04-02 12:14:19 -07002185
Doug Zongker37974732010-09-16 17:44:38 -07002186def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002187 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002188
Tao Bao9dd909e2017-11-14 11:27:32 -08002189 For non-AVB images, raise exception if the data is too big. Print a warning
2190 if the data is nearing the maximum size.
2191
2192 For AVB images, the actual image size should be identical to the limit.
2193
2194 Args:
2195 data: A string that contains all the data for the partition.
2196 target: The partition name. The ".img" suffix is optional.
2197 info_dict: The dict to be looked up for relevant info.
2198 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002199 if target.endswith(".img"):
2200 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002201 mount_point = "/" + target
2202
Ying Wangf8824af2014-06-03 14:07:27 -07002203 fs_type = None
2204 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002205 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002206 if mount_point == "/userdata":
2207 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002208 p = info_dict["fstab"][mount_point]
2209 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002210 device = p.device
2211 if "/" in device:
2212 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002213 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002214 if not fs_type or not limit:
2215 return
Doug Zongkereef39442009-04-02 12:14:19 -07002216
Andrew Boie0f9aec82012-02-14 09:32:52 -08002217 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002218 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2219 # path.
2220 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2221 if size != limit:
2222 raise ExternalError(
2223 "Mismatching image size for %s: expected %d actual %d" % (
2224 target, limit, size))
2225 else:
2226 pct = float(size) * 100.0 / limit
2227 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2228 if pct >= 99.0:
2229 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002230
2231 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002232 logger.warning("\n WARNING: %s\n", msg)
2233 else:
2234 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002235
2236
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002237def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002238 """Parses the APK certs info from a given target-files zip.
2239
2240 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2241 tuple with the following elements: (1) a dictionary that maps packages to
2242 certs (based on the "certificate" and "private_key" attributes in the file;
2243 (2) a string representing the extension of compressed APKs in the target files
2244 (e.g ".gz", ".bro").
2245
2246 Args:
2247 tf_zip: The input target_files ZipFile (already open).
2248
2249 Returns:
2250 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2251 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2252 no compressed APKs.
2253 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002254 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002255 compressed_extension = None
2256
Tao Bao0f990332017-09-08 19:02:54 -07002257 # META/apkcerts.txt contains the info for _all_ the packages known at build
2258 # time. Filter out the ones that are not installed.
2259 installed_files = set()
2260 for name in tf_zip.namelist():
2261 basename = os.path.basename(name)
2262 if basename:
2263 installed_files.add(basename)
2264
Tao Baoda30cfa2017-12-01 16:19:46 -08002265 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002266 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002267 if not line:
2268 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002269 m = re.match(
2270 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002271 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2272 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002273 line)
2274 if not m:
2275 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002276
Tao Bao818ddf52018-01-05 11:17:34 -08002277 matches = m.groupdict()
2278 cert = matches["CERT"]
2279 privkey = matches["PRIVKEY"]
2280 name = matches["NAME"]
2281 this_compressed_extension = matches["COMPRESSED"]
2282
2283 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2284 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2285 if cert in SPECIAL_CERT_STRINGS and not privkey:
2286 certmap[name] = cert
2287 elif (cert.endswith(OPTIONS.public_key_suffix) and
2288 privkey.endswith(OPTIONS.private_key_suffix) and
2289 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2290 certmap[name] = cert[:-public_key_suffix_len]
2291 else:
2292 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2293
2294 if not this_compressed_extension:
2295 continue
2296
2297 # Only count the installed files.
2298 filename = name + '.' + this_compressed_extension
2299 if filename not in installed_files:
2300 continue
2301
2302 # Make sure that all the values in the compression map have the same
2303 # extension. We don't support multiple compression methods in the same
2304 # system image.
2305 if compressed_extension:
2306 if this_compressed_extension != compressed_extension:
2307 raise ValueError(
2308 "Multiple compressed extensions: {} vs {}".format(
2309 compressed_extension, this_compressed_extension))
2310 else:
2311 compressed_extension = this_compressed_extension
2312
2313 return (certmap,
2314 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002315
2316
Doug Zongkereef39442009-04-02 12:14:19 -07002317COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002318Global options
2319
2320 -p (--path) <dir>
2321 Prepend <dir>/bin to the list of places to search for binaries run by this
2322 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002323
Doug Zongker05d3dea2009-06-22 11:32:31 -07002324 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002325 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002326
Tao Bao30df8b42018-04-23 15:32:53 -07002327 -x (--extra) <key=value>
2328 Add a key/value pair to the 'extras' dict, which device-specific extension
2329 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002330
Doug Zongkereef39442009-04-02 12:14:19 -07002331 -v (--verbose)
2332 Show command lines being executed.
2333
2334 -h (--help)
2335 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002336
2337 --logfile <file>
2338 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002339"""
2340
Kelvin Zhang0876c412020-06-23 15:06:58 -04002341
Doug Zongkereef39442009-04-02 12:14:19 -07002342def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002343 print(docstring.rstrip("\n"))
2344 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002345
2346
2347def ParseOptions(argv,
2348 docstring,
2349 extra_opts="", extra_long_opts=(),
2350 extra_option_handler=None):
2351 """Parse the options in argv and return any arguments that aren't
2352 flags. docstring is the calling module's docstring, to be displayed
2353 for errors and -h. extra_opts and extra_long_opts are for flags
2354 defined by the caller, which are processed by passing them to
2355 extra_option_handler."""
2356
2357 try:
2358 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002359 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002360 ["help", "verbose", "path=", "signapk_path=",
2361 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002362 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002363 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2364 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002365 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2366 "aftl_key_path=", "aftl_manufacturer_key_path=",
2367 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002368 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002369 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002370 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002371 sys.exit(2)
2372
Doug Zongkereef39442009-04-02 12:14:19 -07002373 for o, a in opts:
2374 if o in ("-h", "--help"):
2375 Usage(docstring)
2376 sys.exit()
2377 elif o in ("-v", "--verbose"):
2378 OPTIONS.verbose = True
2379 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002380 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002381 elif o in ("--signapk_path",):
2382 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002383 elif o in ("--signapk_shared_library_path",):
2384 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002385 elif o in ("--extra_signapk_args",):
2386 OPTIONS.extra_signapk_args = shlex.split(a)
2387 elif o in ("--java_path",):
2388 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002389 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002390 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002391 elif o in ("--android_jar_path",):
2392 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002393 elif o in ("--public_key_suffix",):
2394 OPTIONS.public_key_suffix = a
2395 elif o in ("--private_key_suffix",):
2396 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002397 elif o in ("--boot_signer_path",):
2398 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002399 elif o in ("--boot_signer_args",):
2400 OPTIONS.boot_signer_args = shlex.split(a)
2401 elif o in ("--verity_signer_path",):
2402 OPTIONS.verity_signer_path = a
2403 elif o in ("--verity_signer_args",):
2404 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002405 elif o in ("--aftl_tool_path",):
2406 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002407 elif o in ("--aftl_server",):
2408 OPTIONS.aftl_server = a
2409 elif o in ("--aftl_key_path",):
2410 OPTIONS.aftl_key_path = a
2411 elif o in ("--aftl_manufacturer_key_path",):
2412 OPTIONS.aftl_manufacturer_key_path = a
2413 elif o in ("--aftl_signer_helper",):
2414 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002415 elif o in ("-s", "--device_specific"):
2416 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002417 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002418 key, value = a.split("=", 1)
2419 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002420 elif o in ("--logfile",):
2421 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002422 else:
2423 if extra_option_handler is None or not extra_option_handler(o, a):
2424 assert False, "unknown option \"%s\"" % (o,)
2425
Doug Zongker85448772014-09-09 14:59:20 -07002426 if OPTIONS.search_path:
2427 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2428 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002429
2430 return args
2431
2432
Tao Bao4c851b12016-09-19 13:54:38 -07002433def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002434 """Make a temp file and add it to the list of things to be deleted
2435 when Cleanup() is called. Return the filename."""
2436 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2437 os.close(fd)
2438 OPTIONS.tempfiles.append(fn)
2439 return fn
2440
2441
Tao Bao1c830bf2017-12-25 10:43:47 -08002442def MakeTempDir(prefix='tmp', suffix=''):
2443 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2444
2445 Returns:
2446 The absolute pathname of the new directory.
2447 """
2448 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2449 OPTIONS.tempfiles.append(dir_name)
2450 return dir_name
2451
2452
Doug Zongkereef39442009-04-02 12:14:19 -07002453def Cleanup():
2454 for i in OPTIONS.tempfiles:
2455 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002456 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002457 else:
2458 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002459 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002460
2461
2462class PasswordManager(object):
2463 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002464 self.editor = os.getenv("EDITOR")
2465 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002466
2467 def GetPasswords(self, items):
2468 """Get passwords corresponding to each string in 'items',
2469 returning a dict. (The dict may have keys in addition to the
2470 values in 'items'.)
2471
2472 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2473 user edit that file to add more needed passwords. If no editor is
2474 available, or $ANDROID_PW_FILE isn't define, prompts the user
2475 interactively in the ordinary way.
2476 """
2477
2478 current = self.ReadFile()
2479
2480 first = True
2481 while True:
2482 missing = []
2483 for i in items:
2484 if i not in current or not current[i]:
2485 missing.append(i)
2486 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002487 if not missing:
2488 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002489
2490 for i in missing:
2491 current[i] = ""
2492
2493 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002494 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002495 if sys.version_info[0] >= 3:
2496 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002497 answer = raw_input("try to edit again? [y]> ").strip()
2498 if answer and answer[0] not in 'yY':
2499 raise RuntimeError("key passwords unavailable")
2500 first = False
2501
2502 current = self.UpdateAndReadFile(current)
2503
Kelvin Zhang0876c412020-06-23 15:06:58 -04002504 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002505 """Prompt the user to enter a value (password) for each key in
2506 'current' whose value is fales. Returns a new dict with all the
2507 values.
2508 """
2509 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002510 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002511 if v:
2512 result[k] = v
2513 else:
2514 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002515 result[k] = getpass.getpass(
2516 "Enter password for %s key> " % k).strip()
2517 if result[k]:
2518 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002519 return result
2520
2521 def UpdateAndReadFile(self, current):
2522 if not self.editor or not self.pwfile:
2523 return self.PromptResult(current)
2524
2525 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002526 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002527 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2528 f.write("# (Additional spaces are harmless.)\n\n")
2529
2530 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002531 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002532 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002533 f.write("[[[ %s ]]] %s\n" % (v, k))
2534 if not v and first_line is None:
2535 # position cursor on first line with no password.
2536 first_line = i + 4
2537 f.close()
2538
Tao Bao986ee862018-10-04 15:46:16 -07002539 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002540
2541 return self.ReadFile()
2542
2543 def ReadFile(self):
2544 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002545 if self.pwfile is None:
2546 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002547 try:
2548 f = open(self.pwfile, "r")
2549 for line in f:
2550 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002551 if not line or line[0] == '#':
2552 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002553 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2554 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002555 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002556 else:
2557 result[m.group(2)] = m.group(1)
2558 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002559 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002560 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002561 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002562 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002563
2564
Dan Albert8e0178d2015-01-27 15:53:15 -08002565def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2566 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002567
2568 # http://b/18015246
2569 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2570 # for files larger than 2GiB. We can work around this by adjusting their
2571 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2572 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2573 # it isn't clear to me exactly what circumstances cause this).
2574 # `zipfile.write()` must be used directly to work around this.
2575 #
2576 # This mess can be avoided if we port to python3.
2577 saved_zip64_limit = zipfile.ZIP64_LIMIT
2578 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2579
2580 if compress_type is None:
2581 compress_type = zip_file.compression
2582 if arcname is None:
2583 arcname = filename
2584
2585 saved_stat = os.stat(filename)
2586
2587 try:
2588 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2589 # file to be zipped and reset it when we're done.
2590 os.chmod(filename, perms)
2591
2592 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002593 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2594 # intentional. zip stores datetimes in local time without a time zone
2595 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2596 # in the zip archive.
2597 local_epoch = datetime.datetime.fromtimestamp(0)
2598 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002599 os.utime(filename, (timestamp, timestamp))
2600
2601 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2602 finally:
2603 os.chmod(filename, saved_stat.st_mode)
2604 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2605 zipfile.ZIP64_LIMIT = saved_zip64_limit
2606
2607
Tao Bao58c1b962015-05-20 09:32:18 -07002608def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002609 compress_type=None):
2610 """Wrap zipfile.writestr() function to work around the zip64 limit.
2611
2612 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2613 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2614 when calling crc32(bytes).
2615
2616 But it still works fine to write a shorter string into a large zip file.
2617 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2618 when we know the string won't be too long.
2619 """
2620
2621 saved_zip64_limit = zipfile.ZIP64_LIMIT
2622 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2623
2624 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2625 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002626 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002627 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002628 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002629 else:
Tao Baof3282b42015-04-01 11:21:55 -07002630 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002631 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2632 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2633 # such a case (since
2634 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2635 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2636 # permission bits. We follow the logic in Python 3 to get consistent
2637 # behavior between using the two versions.
2638 if not zinfo.external_attr:
2639 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002640
2641 # If compress_type is given, it overrides the value in zinfo.
2642 if compress_type is not None:
2643 zinfo.compress_type = compress_type
2644
Tao Bao58c1b962015-05-20 09:32:18 -07002645 # If perms is given, it has a priority.
2646 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002647 # If perms doesn't set the file type, mark it as a regular file.
2648 if perms & 0o770000 == 0:
2649 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002650 zinfo.external_attr = perms << 16
2651
Tao Baof3282b42015-04-01 11:21:55 -07002652 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002653 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2654
Dan Albert8b72aef2015-03-23 19:13:21 -07002655 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002656 zipfile.ZIP64_LIMIT = saved_zip64_limit
2657
2658
Tao Bao89d7ab22017-12-14 17:05:33 -08002659def ZipDelete(zip_filename, entries):
2660 """Deletes entries from a ZIP file.
2661
2662 Since deleting entries from a ZIP file is not supported, it shells out to
2663 'zip -d'.
2664
2665 Args:
2666 zip_filename: The name of the ZIP file.
2667 entries: The name of the entry, or the list of names to be deleted.
2668
2669 Raises:
2670 AssertionError: In case of non-zero return from 'zip'.
2671 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002672 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002673 entries = [entries]
2674 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002675 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002676
2677
Tao Baof3282b42015-04-01 11:21:55 -07002678def ZipClose(zip_file):
2679 # http://b/18015246
2680 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2681 # central directory.
2682 saved_zip64_limit = zipfile.ZIP64_LIMIT
2683 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2684
2685 zip_file.close()
2686
2687 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002688
2689
2690class DeviceSpecificParams(object):
2691 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002692
Doug Zongker05d3dea2009-06-22 11:32:31 -07002693 def __init__(self, **kwargs):
2694 """Keyword arguments to the constructor become attributes of this
2695 object, which is passed to all functions in the device-specific
2696 module."""
Tao Bao38884282019-07-10 22:20:56 -07002697 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002698 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002699 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002700
2701 if self.module is None:
2702 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002703 if not path:
2704 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002705 try:
2706 if os.path.isdir(path):
2707 info = imp.find_module("releasetools", [path])
2708 else:
2709 d, f = os.path.split(path)
2710 b, x = os.path.splitext(f)
2711 if x == ".py":
2712 f = b
2713 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002714 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002715 self.module = imp.load_module("device_specific", *info)
2716 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002717 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002718
2719 def _DoCall(self, function_name, *args, **kwargs):
2720 """Call the named function in the device-specific module, passing
2721 the given args and kwargs. The first argument to the call will be
2722 the DeviceSpecific object itself. If there is no module, or the
2723 module does not define the function, return the value of the
2724 'default' kwarg (which itself defaults to None)."""
2725 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002726 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002727 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2728
2729 def FullOTA_Assertions(self):
2730 """Called after emitting the block of assertions at the top of a
2731 full OTA package. Implementations can add whatever additional
2732 assertions they like."""
2733 return self._DoCall("FullOTA_Assertions")
2734
Doug Zongkere5ff5902012-01-17 10:55:37 -08002735 def FullOTA_InstallBegin(self):
2736 """Called at the start of full OTA installation."""
2737 return self._DoCall("FullOTA_InstallBegin")
2738
Yifan Hong10c530d2018-12-27 17:34:18 -08002739 def FullOTA_GetBlockDifferences(self):
2740 """Called during full OTA installation and verification.
2741 Implementation should return a list of BlockDifference objects describing
2742 the update on each additional partitions.
2743 """
2744 return self._DoCall("FullOTA_GetBlockDifferences")
2745
Doug Zongker05d3dea2009-06-22 11:32:31 -07002746 def FullOTA_InstallEnd(self):
2747 """Called at the end of full OTA installation; typically this is
2748 used to install the image for the device's baseband processor."""
2749 return self._DoCall("FullOTA_InstallEnd")
2750
2751 def IncrementalOTA_Assertions(self):
2752 """Called after emitting the block of assertions at the top of an
2753 incremental OTA package. Implementations can add whatever
2754 additional assertions they like."""
2755 return self._DoCall("IncrementalOTA_Assertions")
2756
Doug Zongkere5ff5902012-01-17 10:55:37 -08002757 def IncrementalOTA_VerifyBegin(self):
2758 """Called at the start of the verification phase of incremental
2759 OTA installation; additional checks can be placed here to abort
2760 the script before any changes are made."""
2761 return self._DoCall("IncrementalOTA_VerifyBegin")
2762
Doug Zongker05d3dea2009-06-22 11:32:31 -07002763 def IncrementalOTA_VerifyEnd(self):
2764 """Called at the end of the verification phase of incremental OTA
2765 installation; additional checks can be placed here to abort the
2766 script before any changes are made."""
2767 return self._DoCall("IncrementalOTA_VerifyEnd")
2768
Doug Zongkere5ff5902012-01-17 10:55:37 -08002769 def IncrementalOTA_InstallBegin(self):
2770 """Called at the start of incremental OTA installation (after
2771 verification is complete)."""
2772 return self._DoCall("IncrementalOTA_InstallBegin")
2773
Yifan Hong10c530d2018-12-27 17:34:18 -08002774 def IncrementalOTA_GetBlockDifferences(self):
2775 """Called during incremental OTA installation and verification.
2776 Implementation should return a list of BlockDifference objects describing
2777 the update on each additional partitions.
2778 """
2779 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2780
Doug Zongker05d3dea2009-06-22 11:32:31 -07002781 def IncrementalOTA_InstallEnd(self):
2782 """Called at the end of incremental OTA installation; typically
2783 this is used to install the image for the device's baseband
2784 processor."""
2785 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002786
Tao Bao9bc6bb22015-11-09 16:58:28 -08002787 def VerifyOTA_Assertions(self):
2788 return self._DoCall("VerifyOTA_Assertions")
2789
Tao Bao76def242017-11-21 09:25:31 -08002790
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002791class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002792 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002793 self.name = name
2794 self.data = data
2795 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002796 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002797 self.sha1 = sha1(data).hexdigest()
2798
2799 @classmethod
2800 def FromLocalFile(cls, name, diskname):
2801 f = open(diskname, "rb")
2802 data = f.read()
2803 f.close()
2804 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002805
2806 def WriteToTemp(self):
2807 t = tempfile.NamedTemporaryFile()
2808 t.write(self.data)
2809 t.flush()
2810 return t
2811
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002812 def WriteToDir(self, d):
2813 with open(os.path.join(d, self.name), "wb") as fp:
2814 fp.write(self.data)
2815
Geremy Condra36bd3652014-02-06 19:45:10 -08002816 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002817 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002818
Tao Bao76def242017-11-21 09:25:31 -08002819
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002820DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002821 ".gz": "imgdiff",
2822 ".zip": ["imgdiff", "-z"],
2823 ".jar": ["imgdiff", "-z"],
2824 ".apk": ["imgdiff", "-z"],
2825 ".img": "imgdiff",
2826}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002827
Tao Bao76def242017-11-21 09:25:31 -08002828
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002829class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002830 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002831 self.tf = tf
2832 self.sf = sf
2833 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002834 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002835
2836 def ComputePatch(self):
2837 """Compute the patch (as a string of data) needed to turn sf into
2838 tf. Returns the same tuple as GetPatch()."""
2839
2840 tf = self.tf
2841 sf = self.sf
2842
Doug Zongker24cd2802012-08-14 16:36:15 -07002843 if self.diff_program:
2844 diff_program = self.diff_program
2845 else:
2846 ext = os.path.splitext(tf.name)[1]
2847 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002848
2849 ttemp = tf.WriteToTemp()
2850 stemp = sf.WriteToTemp()
2851
2852 ext = os.path.splitext(tf.name)[1]
2853
2854 try:
2855 ptemp = tempfile.NamedTemporaryFile()
2856 if isinstance(diff_program, list):
2857 cmd = copy.copy(diff_program)
2858 else:
2859 cmd = [diff_program]
2860 cmd.append(stemp.name)
2861 cmd.append(ttemp.name)
2862 cmd.append(ptemp.name)
2863 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002864 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002865
Doug Zongkerf8340082014-08-05 10:39:37 -07002866 def run():
2867 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002868 if e:
2869 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002870 th = threading.Thread(target=run)
2871 th.start()
2872 th.join(timeout=300) # 5 mins
2873 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002874 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002875 p.terminate()
2876 th.join(5)
2877 if th.is_alive():
2878 p.kill()
2879 th.join()
2880
Tianjie Xua2a9f992018-01-05 15:15:54 -08002881 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002882 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002883 self.patch = None
2884 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002885 diff = ptemp.read()
2886 finally:
2887 ptemp.close()
2888 stemp.close()
2889 ttemp.close()
2890
2891 self.patch = diff
2892 return self.tf, self.sf, self.patch
2893
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002894 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002895 """Returns a tuple of (target_file, source_file, patch_data).
2896
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002897 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002898 computing the patch failed.
2899 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002900 return self.tf, self.sf, self.patch
2901
2902
2903def ComputeDifferences(diffs):
2904 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002905 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002906
2907 # Do the largest files first, to try and reduce the long-pole effect.
2908 by_size = [(i.tf.size, i) for i in diffs]
2909 by_size.sort(reverse=True)
2910 by_size = [i[1] for i in by_size]
2911
2912 lock = threading.Lock()
2913 diff_iter = iter(by_size) # accessed under lock
2914
2915 def worker():
2916 try:
2917 lock.acquire()
2918 for d in diff_iter:
2919 lock.release()
2920 start = time.time()
2921 d.ComputePatch()
2922 dur = time.time() - start
2923 lock.acquire()
2924
2925 tf, sf, patch = d.GetPatch()
2926 if sf.name == tf.name:
2927 name = tf.name
2928 else:
2929 name = "%s (%s)" % (tf.name, sf.name)
2930 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002931 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002932 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002933 logger.info(
2934 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2935 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002936 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002937 except Exception:
2938 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002939 raise
2940
2941 # start worker threads; wait for them all to finish.
2942 threads = [threading.Thread(target=worker)
2943 for i in range(OPTIONS.worker_threads)]
2944 for th in threads:
2945 th.start()
2946 while threads:
2947 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002948
2949
Dan Albert8b72aef2015-03-23 19:13:21 -07002950class BlockDifference(object):
2951 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002952 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002953 self.tgt = tgt
2954 self.src = src
2955 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002956 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002957 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002958
Tao Baodd2a5892015-03-12 12:32:37 -07002959 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002960 version = max(
2961 int(i) for i in
2962 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002963 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002964 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002965
Tianjie Xu41976c72019-07-03 13:57:01 -07002966 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2967 version=self.version,
2968 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002969 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002970 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002971 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002972 self.touched_src_ranges = b.touched_src_ranges
2973 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002974
Yifan Hong10c530d2018-12-27 17:34:18 -08002975 # On devices with dynamic partitions, for new partitions,
2976 # src is None but OPTIONS.source_info_dict is not.
2977 if OPTIONS.source_info_dict is None:
2978 is_dynamic_build = OPTIONS.info_dict.get(
2979 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002980 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002981 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002982 is_dynamic_build = OPTIONS.source_info_dict.get(
2983 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002984 is_dynamic_source = partition in shlex.split(
2985 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002986
Yifan Hongbb2658d2019-01-25 12:30:58 -08002987 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002988 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2989
Yifan Hongbb2658d2019-01-25 12:30:58 -08002990 # For dynamic partitions builds, check partition list in both source
2991 # and target build because new partitions may be added, and existing
2992 # partitions may be removed.
2993 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2994
Yifan Hong10c530d2018-12-27 17:34:18 -08002995 if is_dynamic:
2996 self.device = 'map_partition("%s")' % partition
2997 else:
2998 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002999 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3000 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003001 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003002 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3003 OPTIONS.source_info_dict)
3004 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003005
Tao Baod8d14be2016-02-04 14:26:02 -08003006 @property
3007 def required_cache(self):
3008 return self._required_cache
3009
Tao Bao76def242017-11-21 09:25:31 -08003010 def WriteScript(self, script, output_zip, progress=None,
3011 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003012 if not self.src:
3013 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003014 script.Print("Patching %s image unconditionally..." % (self.partition,))
3015 else:
3016 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003017
Dan Albert8b72aef2015-03-23 19:13:21 -07003018 if progress:
3019 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003020 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003021
3022 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003023 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003024
Tao Bao9bc6bb22015-11-09 16:58:28 -08003025 def WriteStrictVerifyScript(self, script):
3026 """Verify all the blocks in the care_map, including clobbered blocks.
3027
3028 This differs from the WriteVerifyScript() function: a) it prints different
3029 error messages; b) it doesn't allow half-way updated images to pass the
3030 verification."""
3031
3032 partition = self.partition
3033 script.Print("Verifying %s..." % (partition,))
3034 ranges = self.tgt.care_map
3035 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003036 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003037 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3038 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003039 self.device, ranges_str,
3040 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003041 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003042 script.AppendExtra("")
3043
Tao Baod522bdc2016-04-12 15:53:16 -07003044 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003045 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003046
3047 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003048 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003049 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003050
3051 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003052 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003053 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003054 ranges = self.touched_src_ranges
3055 expected_sha1 = self.touched_src_sha1
3056 else:
3057 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3058 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003059
3060 # No blocks to be checked, skipping.
3061 if not ranges:
3062 return
3063
Tao Bao5ece99d2015-05-12 11:42:31 -07003064 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003065 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003066 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003067 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3068 '"%s.patch.dat")) then' % (
3069 self.device, ranges_str, expected_sha1,
3070 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003071 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003072 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003073
Tianjie Xufc3422a2015-12-15 11:53:59 -08003074 if self.version >= 4:
3075
3076 # Bug: 21124327
3077 # When generating incrementals for the system and vendor partitions in
3078 # version 4 or newer, explicitly check the first block (which contains
3079 # the superblock) of the partition to see if it's what we expect. If
3080 # this check fails, give an explicit log message about the partition
3081 # having been remounted R/W (the most likely explanation).
3082 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003083 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003084
3085 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003086 if partition == "system":
3087 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3088 else:
3089 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003090 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003091 'ifelse (block_image_recover({device}, "{ranges}") && '
3092 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003093 'package_extract_file("{partition}.transfer.list"), '
3094 '"{partition}.new.dat", "{partition}.patch.dat"), '
3095 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003096 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003097 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003098 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003099
Tao Baodd2a5892015-03-12 12:32:37 -07003100 # Abort the OTA update. Note that the incremental OTA cannot be applied
3101 # even if it may match the checksum of the target partition.
3102 # a) If version < 3, operations like move and erase will make changes
3103 # unconditionally and damage the partition.
3104 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003105 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003106 if partition == "system":
3107 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3108 else:
3109 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3110 script.AppendExtra((
3111 'abort("E%d: %s partition has unexpected contents");\n'
3112 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003113
Yifan Hong10c530d2018-12-27 17:34:18 -08003114 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003115 partition = self.partition
3116 script.Print('Verifying the updated %s image...' % (partition,))
3117 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3118 ranges = self.tgt.care_map
3119 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003120 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003121 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003122 self.device, ranges_str,
3123 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003124
3125 # Bug: 20881595
3126 # Verify that extended blocks are really zeroed out.
3127 if self.tgt.extended:
3128 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003129 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003130 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003131 self.device, ranges_str,
3132 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003133 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003134 if partition == "system":
3135 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3136 else:
3137 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003138 script.AppendExtra(
3139 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003140 ' abort("E%d: %s partition has unexpected non-zero contents after '
3141 'OTA update");\n'
3142 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003143 else:
3144 script.Print('Verified the updated %s image.' % (partition,))
3145
Tianjie Xu209db462016-05-24 17:34:52 -07003146 if partition == "system":
3147 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3148 else:
3149 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3150
Tao Bao5fcaaef2015-06-01 13:40:49 -07003151 script.AppendExtra(
3152 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003153 ' abort("E%d: %s partition has unexpected contents after OTA '
3154 'update");\n'
3155 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003156
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003157 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003158 ZipWrite(output_zip,
3159 '{}.transfer.list'.format(self.path),
3160 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003161
Tao Bao76def242017-11-21 09:25:31 -08003162 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3163 # its size. Quailty 9 almost triples the compression time but doesn't
3164 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003165 # zip | brotli(quality 6) | brotli(quality 9)
3166 # compressed_size: 942M | 869M (~8% reduced) | 854M
3167 # compression_time: 75s | 265s | 719s
3168 # decompression_time: 15s | 25s | 25s
3169
3170 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003171 brotli_cmd = ['brotli', '--quality=6',
3172 '--output={}.new.dat.br'.format(self.path),
3173 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003174 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003175 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003176
3177 new_data_name = '{}.new.dat.br'.format(self.partition)
3178 ZipWrite(output_zip,
3179 '{}.new.dat.br'.format(self.path),
3180 new_data_name,
3181 compress_type=zipfile.ZIP_STORED)
3182 else:
3183 new_data_name = '{}.new.dat'.format(self.partition)
3184 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3185
Dan Albert8e0178d2015-01-27 15:53:15 -08003186 ZipWrite(output_zip,
3187 '{}.patch.dat'.format(self.path),
3188 '{}.patch.dat'.format(self.partition),
3189 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003190
Tianjie Xu209db462016-05-24 17:34:52 -07003191 if self.partition == "system":
3192 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3193 else:
3194 code = ErrorCode.VENDOR_UPDATE_FAILURE
3195
Yifan Hong10c530d2018-12-27 17:34:18 -08003196 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003197 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003198 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003199 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003200 device=self.device, partition=self.partition,
3201 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003202 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003203
Kelvin Zhang0876c412020-06-23 15:06:58 -04003204 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003205 data = source.ReadRangeSet(ranges)
3206 ctx = sha1()
3207
3208 for p in data:
3209 ctx.update(p)
3210
3211 return ctx.hexdigest()
3212
Kelvin Zhang0876c412020-06-23 15:06:58 -04003213 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003214 """Return the hash value for all zero blocks."""
3215 zero_block = '\x00' * 4096
3216 ctx = sha1()
3217 for _ in range(num_blocks):
3218 ctx.update(zero_block)
3219
3220 return ctx.hexdigest()
3221
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003222
Tianjie Xu41976c72019-07-03 13:57:01 -07003223# Expose these two classes to support vendor-specific scripts
3224DataImage = images.DataImage
3225EmptyImage = images.EmptyImage
3226
Tao Bao76def242017-11-21 09:25:31 -08003227
Doug Zongker96a57e72010-09-26 14:57:41 -07003228# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003229PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003230 "ext4": "EMMC",
3231 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003232 "f2fs": "EMMC",
3233 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003234}
Doug Zongker96a57e72010-09-26 14:57:41 -07003235
Kelvin Zhang0876c412020-06-23 15:06:58 -04003236
Yifan Hongbdb32012020-05-07 12:38:53 -07003237def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3238 """
3239 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3240 backwards compatibility. It aborts if the fstab entry has slotselect option
3241 (unless check_no_slot is explicitly set to False).
3242 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003243 fstab = info["fstab"]
3244 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003245 if check_no_slot:
3246 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003247 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003248 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3249 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003250 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003251
3252
Yifan Hongbdb32012020-05-07 12:38:53 -07003253def GetTypeAndDeviceExpr(mount_point, info):
3254 """
3255 Return the filesystem of the partition, and an edify expression that evaluates
3256 to the device at runtime.
3257 """
3258 fstab = info["fstab"]
3259 if fstab:
3260 p = fstab[mount_point]
3261 device_expr = '"%s"' % fstab[mount_point].device
3262 if p.slotselect:
3263 device_expr = 'add_slot_suffix(%s)' % device_expr
3264 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003265 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003266
3267
3268def GetEntryForDevice(fstab, device):
3269 """
3270 Returns:
3271 The first entry in fstab whose device is the given value.
3272 """
3273 if not fstab:
3274 return None
3275 for mount_point in fstab:
3276 if fstab[mount_point].device == device:
3277 return fstab[mount_point]
3278 return None
3279
Kelvin Zhang0876c412020-06-23 15:06:58 -04003280
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003281def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003282 """Parses and converts a PEM-encoded certificate into DER-encoded.
3283
3284 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3285
3286 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003287 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003288 """
3289 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003290 save = False
3291 for line in data.split("\n"):
3292 if "--END CERTIFICATE--" in line:
3293 break
3294 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003295 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003296 if "--BEGIN CERTIFICATE--" in line:
3297 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003298 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003299 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003300
Tao Bao04e1f012018-02-04 12:13:35 -08003301
3302def ExtractPublicKey(cert):
3303 """Extracts the public key (PEM-encoded) from the given certificate file.
3304
3305 Args:
3306 cert: The certificate filename.
3307
3308 Returns:
3309 The public key string.
3310
3311 Raises:
3312 AssertionError: On non-zero return from 'openssl'.
3313 """
3314 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3315 # While openssl 1.1 writes the key into the given filename followed by '-out',
3316 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3317 # stdout instead.
3318 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3319 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3320 pubkey, stderrdata = proc.communicate()
3321 assert proc.returncode == 0, \
3322 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3323 return pubkey
3324
3325
Tao Bao1ac886e2019-06-26 11:58:22 -07003326def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003327 """Extracts the AVB public key from the given public or private key.
3328
3329 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003330 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003331 key: The input key file, which should be PEM-encoded public or private key.
3332
3333 Returns:
3334 The path to the extracted AVB public key file.
3335 """
3336 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3337 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003338 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003339 return output
3340
3341
Doug Zongker412c02f2014-02-13 10:58:24 -08003342def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3343 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003344 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003345
Tao Bao6d5d6232018-03-09 17:04:42 -08003346 Most of the space in the boot and recovery images is just the kernel, which is
3347 identical for the two, so the resulting patch should be efficient. Add it to
3348 the output zip, along with a shell script that is run from init.rc on first
3349 boot to actually do the patching and install the new recovery image.
3350
3351 Args:
3352 input_dir: The top-level input directory of the target-files.zip.
3353 output_sink: The callback function that writes the result.
3354 recovery_img: File object for the recovery image.
3355 boot_img: File objects for the boot image.
3356 info_dict: A dict returned by common.LoadInfoDict() on the input
3357 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003358 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003359 if info_dict is None:
3360 info_dict = OPTIONS.info_dict
3361
Tao Bao6d5d6232018-03-09 17:04:42 -08003362 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003363 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3364
3365 if board_uses_vendorimage:
3366 # In this case, the output sink is rooted at VENDOR
3367 recovery_img_path = "etc/recovery.img"
3368 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3369 sh_dir = "bin"
3370 else:
3371 # In this case the output sink is rooted at SYSTEM
3372 recovery_img_path = "vendor/etc/recovery.img"
3373 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3374 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003375
Tao Baof2cffbd2015-07-22 12:33:18 -07003376 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003377 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003378
3379 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003380 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003381 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003382 # With system-root-image, boot and recovery images will have mismatching
3383 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3384 # to handle such a case.
3385 if system_root_image:
3386 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003387 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003388 assert not os.path.exists(path)
3389 else:
3390 diff_program = ["imgdiff"]
3391 if os.path.exists(path):
3392 diff_program.append("-b")
3393 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003394 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003395 else:
3396 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003397
3398 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3399 _, _, patch = d.ComputePatch()
3400 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003401
Dan Albertebb19aa2015-03-27 19:11:53 -07003402 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003403 # The following GetTypeAndDevice()s need to use the path in the target
3404 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003405 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3406 check_no_slot=False)
3407 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3408 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003409 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003410 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003411
Tao Baof2cffbd2015-07-22 12:33:18 -07003412 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003413
3414 # Note that we use /vendor to refer to the recovery resources. This will
3415 # work for a separate vendor partition mounted at /vendor or a
3416 # /system/vendor subdirectory on the system partition, for which init will
3417 # create a symlink from /vendor to /system/vendor.
3418
3419 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003420if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3421 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003422 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003423 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3424 log -t recovery "Installing new recovery image: succeeded" || \\
3425 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003426else
3427 log -t recovery "Recovery image already installed"
3428fi
3429""" % {'type': recovery_type,
3430 'device': recovery_device,
3431 'sha1': recovery_img.sha1,
3432 'size': recovery_img.size}
3433 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003434 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003435if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3436 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003437 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003438 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3439 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3440 log -t recovery "Installing new recovery image: succeeded" || \\
3441 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003442else
3443 log -t recovery "Recovery image already installed"
3444fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003445""" % {'boot_size': boot_img.size,
3446 'boot_sha1': boot_img.sha1,
3447 'recovery_size': recovery_img.size,
3448 'recovery_sha1': recovery_img.sha1,
3449 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003450 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003451 'recovery_type': recovery_type,
3452 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003453 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003454
Bill Peckhame868aec2019-09-17 17:06:47 -07003455 # The install script location moved from /system/etc to /system/bin in the L
3456 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3457 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003458
Tao Bao32fcdab2018-10-12 10:30:39 -07003459 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003460
Tao Baoda30cfa2017-12-01 16:19:46 -08003461 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003462
3463
3464class DynamicPartitionUpdate(object):
3465 def __init__(self, src_group=None, tgt_group=None, progress=None,
3466 block_difference=None):
3467 self.src_group = src_group
3468 self.tgt_group = tgt_group
3469 self.progress = progress
3470 self.block_difference = block_difference
3471
3472 @property
3473 def src_size(self):
3474 if not self.block_difference:
3475 return 0
3476 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3477
3478 @property
3479 def tgt_size(self):
3480 if not self.block_difference:
3481 return 0
3482 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3483
3484 @staticmethod
3485 def _GetSparseImageSize(img):
3486 if not img:
3487 return 0
3488 return img.blocksize * img.total_blocks
3489
3490
3491class DynamicGroupUpdate(object):
3492 def __init__(self, src_size=None, tgt_size=None):
3493 # None: group does not exist. 0: no size limits.
3494 self.src_size = src_size
3495 self.tgt_size = tgt_size
3496
3497
3498class DynamicPartitionsDifference(object):
3499 def __init__(self, info_dict, block_diffs, progress_dict=None,
3500 source_info_dict=None):
3501 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003502 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003503
3504 self._remove_all_before_apply = False
3505 if source_info_dict is None:
3506 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003507 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003508
Tao Baof1113e92019-06-18 12:10:14 -07003509 block_diff_dict = collections.OrderedDict(
3510 [(e.partition, e) for e in block_diffs])
3511
Yifan Hong10c530d2018-12-27 17:34:18 -08003512 assert len(block_diff_dict) == len(block_diffs), \
3513 "Duplicated BlockDifference object for {}".format(
3514 [partition for partition, count in
3515 collections.Counter(e.partition for e in block_diffs).items()
3516 if count > 1])
3517
Yifan Hong79997e52019-01-23 16:56:19 -08003518 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003519
3520 for p, block_diff in block_diff_dict.items():
3521 self._partition_updates[p] = DynamicPartitionUpdate()
3522 self._partition_updates[p].block_difference = block_diff
3523
3524 for p, progress in progress_dict.items():
3525 if p in self._partition_updates:
3526 self._partition_updates[p].progress = progress
3527
3528 tgt_groups = shlex.split(info_dict.get(
3529 "super_partition_groups", "").strip())
3530 src_groups = shlex.split(source_info_dict.get(
3531 "super_partition_groups", "").strip())
3532
3533 for g in tgt_groups:
3534 for p in shlex.split(info_dict.get(
Daniel Norman2d7989a2021-04-05 17:40:47 +00003535 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003536 assert p in self._partition_updates, \
3537 "{} is in target super_{}_partition_list but no BlockDifference " \
3538 "object is provided.".format(p, g)
3539 self._partition_updates[p].tgt_group = g
3540
3541 for g in src_groups:
3542 for p in shlex.split(source_info_dict.get(
Daniel Norman2d7989a2021-04-05 17:40:47 +00003543 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003544 assert p in self._partition_updates, \
3545 "{} is in source super_{}_partition_list but no BlockDifference " \
3546 "object is provided.".format(p, g)
3547 self._partition_updates[p].src_group = g
3548
Yifan Hong45433e42019-01-18 13:55:25 -08003549 target_dynamic_partitions = set(shlex.split(info_dict.get(
3550 "dynamic_partition_list", "").strip()))
3551 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3552 if u.tgt_size)
3553 assert block_diffs_with_target == target_dynamic_partitions, \
3554 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3555 list(target_dynamic_partitions), list(block_diffs_with_target))
3556
3557 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3558 "dynamic_partition_list", "").strip()))
3559 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3560 if u.src_size)
3561 assert block_diffs_with_source == source_dynamic_partitions, \
3562 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3563 list(source_dynamic_partitions), list(block_diffs_with_source))
3564
Yifan Hong10c530d2018-12-27 17:34:18 -08003565 if self._partition_updates:
3566 logger.info("Updating dynamic partitions %s",
3567 self._partition_updates.keys())
3568
Yifan Hong79997e52019-01-23 16:56:19 -08003569 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003570
3571 for g in tgt_groups:
3572 self._group_updates[g] = DynamicGroupUpdate()
3573 self._group_updates[g].tgt_size = int(info_dict.get(
3574 "super_%s_group_size" % g, "0").strip())
3575
3576 for g in src_groups:
3577 if g not in self._group_updates:
3578 self._group_updates[g] = DynamicGroupUpdate()
3579 self._group_updates[g].src_size = int(source_info_dict.get(
3580 "super_%s_group_size" % g, "0").strip())
3581
3582 self._Compute()
3583
3584 def WriteScript(self, script, output_zip, write_verify_script=False):
3585 script.Comment('--- Start patching dynamic partitions ---')
3586 for p, u in self._partition_updates.items():
3587 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3588 script.Comment('Patch partition %s' % p)
3589 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3590 write_verify_script=False)
3591
3592 op_list_path = MakeTempFile()
3593 with open(op_list_path, 'w') as f:
3594 for line in self._op_list:
3595 f.write('{}\n'.format(line))
3596
3597 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3598
3599 script.Comment('Update dynamic partition metadata')
3600 script.AppendExtra('assert(update_dynamic_partitions('
3601 'package_extract_file("dynamic_partitions_op_list")));')
3602
3603 if write_verify_script:
3604 for p, u in self._partition_updates.items():
3605 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3606 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003607 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003608
3609 for p, u in self._partition_updates.items():
3610 if u.tgt_size and u.src_size <= u.tgt_size:
3611 script.Comment('Patch partition %s' % p)
3612 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3613 write_verify_script=write_verify_script)
3614 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003615 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003616
3617 script.Comment('--- End patching dynamic partitions ---')
3618
3619 def _Compute(self):
3620 self._op_list = list()
3621
3622 def append(line):
3623 self._op_list.append(line)
3624
3625 def comment(line):
3626 self._op_list.append("# %s" % line)
3627
3628 if self._remove_all_before_apply:
3629 comment('Remove all existing dynamic partitions and groups before '
3630 'applying full OTA')
3631 append('remove_all_groups')
3632
3633 for p, u in self._partition_updates.items():
3634 if u.src_group and not u.tgt_group:
3635 append('remove %s' % p)
3636
3637 for p, u in self._partition_updates.items():
3638 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3639 comment('Move partition %s from %s to default' % (p, u.src_group))
3640 append('move %s default' % p)
3641
3642 for p, u in self._partition_updates.items():
3643 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3644 comment('Shrink partition %s from %d to %d' %
3645 (p, u.src_size, u.tgt_size))
3646 append('resize %s %s' % (p, u.tgt_size))
3647
3648 for g, u in self._group_updates.items():
3649 if u.src_size is not None and u.tgt_size is None:
3650 append('remove_group %s' % g)
3651 if (u.src_size is not None and u.tgt_size is not None and
Daniel Norman2d7989a2021-04-05 17:40:47 +00003652 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003653 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3654 append('resize_group %s %d' % (g, u.tgt_size))
3655
3656 for g, u in self._group_updates.items():
3657 if u.src_size is None and u.tgt_size is not None:
3658 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3659 append('add_group %s %d' % (g, u.tgt_size))
3660 if (u.src_size is not None and u.tgt_size is not None and
Daniel Norman2d7989a2021-04-05 17:40:47 +00003661 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003662 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3663 append('resize_group %s %d' % (g, u.tgt_size))
3664
3665 for p, u in self._partition_updates.items():
3666 if u.tgt_group and not u.src_group:
3667 comment('Add partition %s to group %s' % (p, u.tgt_group))
3668 append('add %s %s' % (p, u.tgt_group))
3669
3670 for p, u in self._partition_updates.items():
3671 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003672 comment('Grow partition %s from %d to %d' %
3673 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003674 append('resize %s %d' % (p, u.tgt_size))
3675
3676 for p, u in self._partition_updates.items():
3677 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3678 comment('Move partition %s from default to %s' %
3679 (p, u.tgt_group))
3680 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003681
3682
jiajia tangf3f842b2021-03-17 21:49:44 +08003683def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003684 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003685 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003686
3687 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003688 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003689
3690 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003691 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003692 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003693 tmp_dir = MakeTempDir('boot_', suffix='.img')
3694 try:
Daniel Norman2d7989a2021-04-05 17:40:47 +00003695 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003696 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3697 if not os.path.isfile(ramdisk):
3698 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3699 return None
3700 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003701 if ramdisk_format == RamdiskFormat.LZ4:
3702 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3703 elif ramdisk_format == RamdiskFormat.GZ:
3704 with open(ramdisk, 'rb') as input_stream:
3705 with open(uncompressed_ramdisk, 'wb') as output_stream:
3706 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(), stdout=output_stream.fileno())
3707 p2.wait()
3708 else:
3709 logger.error('Only support lz4 or minigzip ramdisk format.')
3710 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003711
3712 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3713 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3714 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3715 # the host environment.
3716 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Daniel Norman2d7989a2021-04-05 17:40:47 +00003717 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003718
Yifan Hongc65a0542021-01-07 14:21:01 -08003719 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3720 prop_file = os.path.join(extracted_ramdisk, search_path)
3721 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003722 return prop_file
Daniel Norman2d7989a2021-04-05 17:40:47 +00003723 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003724
Yifan Hong7dc51172021-01-12 11:27:39 -08003725 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003726
Yifan Hong85ac5012021-01-07 14:43:46 -08003727 except ExternalError as e:
3728 logger.warning('Unable to get boot image build props: %s', e)
3729 return None
3730
3731
3732def GetBootImageTimestamp(boot_img):
3733 """
3734 Get timestamp from ramdisk within the boot image
3735
3736 Args:
3737 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3738
3739 Return:
3740 An integer that corresponds to the timestamp of the boot image, or None
3741 if file has unknown format. Raise exception if an unexpected error has
3742 occurred.
3743 """
3744 prop_file = GetBootImageBuildProp(boot_img)
3745 if not prop_file:
3746 return None
3747
3748 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3749 if props is None:
3750 return None
3751
3752 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003753 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3754 if timestamp:
3755 return int(timestamp)
Daniel Norman2d7989a2021-04-05 17:40:47 +00003756 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003757 return None
3758
3759 except ExternalError as e:
3760 logger.warning('Unable to get boot image timestamp: %s', e)
3761 return None