blob: c5e1adea120e8dc65d4c12f2df661ecda607665a [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Kelvin Zhangc184fa12021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080071 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.extra_signapk_args = []
73 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080074 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080075 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070076 self.public_key_suffix = ".x509.pem"
77 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070078 # use otatools built boot_signer by default
79 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070080 self.boot_signer_args = []
81 self.verity_signer_path = None
82 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070083 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080084 self.aftl_server = None
85 self.aftl_key_path = None
86 self.aftl_manufacturer_key_path = None
87 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070088 self.verbose = False
89 self.tempfiles = []
90 self.device_specific = None
91 self.extras = {}
92 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070093 self.source_info_dict = None
94 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070095 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070096 # Stash size cannot exceed cache_size * threshold.
97 self.cache_size = None
98 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070099 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -0700100 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700101
102
103OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700104
Tao Bao71197512018-10-11 14:08:45 -0700105# The block size that's used across the releasetools scripts.
106BLOCK_SIZE = 4096
107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800108# Values for "certificate" in apkcerts that mean special things.
109SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
110
Tao Bao5cc0abb2019-03-21 10:18:05 -0700111# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
112# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800113# descriptor into vbmeta.img. When adding a new entry here, the
114# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
115# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000116AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
117 'system', 'system_ext', 'vendor', 'vendor_boot',
118 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800119
Tao Bao08c190f2019-06-03 23:07:58 -0700120# Chained VBMeta partitions.
121AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
122
Tianjie Xu861f4132018-09-12 11:49:33 -0700123# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400124PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700125 'system',
126 'vendor',
127 'product',
128 'system_ext',
129 'odm',
130 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700131 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Tao Bao32fcdab2018-10-12 10:30:39 -0700279 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400280 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700281 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700282 return subprocess.Popen(args, **kwargs)
283
284
Tao Bao986ee862018-10-04 15:46:16 -0700285def RunAndCheckOutput(args, verbose=None, **kwargs):
286 """Runs the given command and returns the output.
287
288 Args:
289 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700290 verbose: Whether the commands should be shown. Default to the global
291 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700292 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
293 stdin, etc. stdout and stderr will default to subprocess.PIPE and
294 subprocess.STDOUT respectively unless caller specifies any of them.
295
296 Returns:
297 The output string.
298
299 Raises:
300 ExternalError: On non-zero exit from the command.
301 """
Tao Bao986ee862018-10-04 15:46:16 -0700302 proc = Run(args, verbose=verbose, **kwargs)
303 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800304 if output is None:
305 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700306 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400307 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700308 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700309 if proc.returncode != 0:
310 raise ExternalError(
311 "Failed to run command '{}' (exit code {}):\n{}".format(
312 args, proc.returncode, output))
313 return output
314
315
Tao Baoc765cca2018-01-31 17:32:40 -0800316def RoundUpTo4K(value):
317 rounded_up = value + 4095
318 return rounded_up - (rounded_up % 4096)
319
320
Ying Wang7e6d4e42010-12-13 16:25:36 -0800321def CloseInheritedPipes():
322 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
323 before doing other work."""
324 if platform.system() != "Darwin":
325 return
326 for d in range(3, 1025):
327 try:
328 stat = os.fstat(d)
329 if stat is not None:
330 pipebit = stat[0] & 0x1000
331 if pipebit != 0:
332 os.close(d)
333 except OSError:
334 pass
335
336
Tao Bao1c320f82019-10-04 23:25:12 -0700337class BuildInfo(object):
338 """A class that holds the information for a given build.
339
340 This class wraps up the property querying for a given source or target build.
341 It abstracts away the logic of handling OEM-specific properties, and caches
342 the commonly used properties such as fingerprint.
343
344 There are two types of info dicts: a) build-time info dict, which is generated
345 at build time (i.e. included in a target_files zip); b) OEM info dict that is
346 specified at package generation time (via command line argument
347 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
348 having "oem_fingerprint_properties" in build-time info dict), all the queries
349 would be answered based on build-time info dict only. Otherwise if using
350 OEM-specific properties, some of them will be calculated from two info dicts.
351
352 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800353 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700354
355 Attributes:
356 info_dict: The build-time info dict.
357 is_ab: Whether it's a build that uses A/B OTA.
358 oem_dicts: A list of OEM dicts.
359 oem_props: A list of OEM properties that should be read from OEM dicts; None
360 if the build doesn't use any OEM-specific property.
361 fingerprint: The fingerprint of the build, which would be calculated based
362 on OEM properties if applicable.
363 device: The device name, which could come from OEM dicts if applicable.
364 """
365
366 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
367 "ro.product.manufacturer", "ro.product.model",
368 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
370 "product", "odm", "vendor", "system_ext", "system"]
371 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
372 "product", "product_services", "odm", "vendor", "system"]
373 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700374
Tao Bao3ed35d32019-10-07 20:48:48 -0700375 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700376 """Initializes a BuildInfo instance with the given dicts.
377
378 Note that it only wraps up the given dicts, without making copies.
379
380 Arguments:
381 info_dict: The build-time info dict.
382 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
383 that it always uses the first dict to calculate the fingerprint or the
384 device name. The rest would be used for asserting OEM properties only
385 (e.g. one package can be installed on one of these devices).
386
387 Raises:
388 ValueError: On invalid inputs.
389 """
390 self.info_dict = info_dict
391 self.oem_dicts = oem_dicts
392
393 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700394
Hongguang Chend7c160f2020-05-03 21:24:26 -0700395 # Skip _oem_props if oem_dicts is None to use BuildInfo in
396 # sign_target_files_apks
397 if self.oem_dicts:
398 self._oem_props = info_dict.get("oem_fingerprint_properties")
399 else:
400 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 def check_fingerprint(fingerprint):
403 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
404 raise ValueError(
405 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
406 "3.2.2. Build Parameters.".format(fingerprint))
407
Daniel Normand5fe8622020-01-08 17:01:11 -0800408 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800409 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800410 try:
411 fingerprint = self.CalculatePartitionFingerprint(partition)
412 check_fingerprint(fingerprint)
413 self._partition_fingerprints[partition] = fingerprint
414 except ExternalError:
415 continue
416 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800417 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 # need a fingerprint when creating the image.
419 self._partition_fingerprints[
420 "system_other"] = self._partition_fingerprints["system"]
421
Tao Bao1c320f82019-10-04 23:25:12 -0700422 # These two should be computed only after setting self._oem_props.
423 self._device = self.GetOemProperty("ro.product.device")
424 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800425 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700426
427 @property
428 def is_ab(self):
429 return self._is_ab
430
431 @property
432 def device(self):
433 return self._device
434
435 @property
436 def fingerprint(self):
437 return self._fingerprint
438
439 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700440 def oem_props(self):
441 return self._oem_props
442
443 def __getitem__(self, key):
444 return self.info_dict[key]
445
446 def __setitem__(self, key, value):
447 self.info_dict[key] = value
448
449 def get(self, key, default=None):
450 return self.info_dict.get(key, default)
451
452 def items(self):
453 return self.info_dict.items()
454
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000455 def _GetRawBuildProp(self, prop, partition):
456 prop_file = '{}.build.prop'.format(
457 partition) if partition else 'build.prop'
458 partition_props = self.info_dict.get(prop_file)
459 if not partition_props:
460 return None
461 return partition_props.GetProp(prop)
462
Daniel Normand5fe8622020-01-08 17:01:11 -0800463 def GetPartitionBuildProp(self, prop, partition):
464 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800465
466 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400467 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800468
Daniel Normand5fe8622020-01-08 17:01:11 -0800469 # If provided a partition for this property, only look within that
470 # partition's build.prop.
471 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800472 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800473 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800474 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000475
476 prop_val = self._GetRawBuildProp(prop, partition)
477 if prop_val is not None:
478 return prop_val
479 raise ExternalError("couldn't find %s in %s.build.prop" %
480 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800481
Tao Bao1c320f82019-10-04 23:25:12 -0700482 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800483 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700484 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
485 return self._ResolveRoProductBuildProp(prop)
486
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000487 prop_val = self._GetRawBuildProp(prop, None)
488 if prop_val is not None:
489 return prop_val
490
491 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700492
493 def _ResolveRoProductBuildProp(self, prop):
494 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000495 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700496 if prop_val:
497 return prop_val
498
Steven Laver8e2086e2020-04-27 16:26:31 -0700499 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000500 source_order_val = self._GetRawBuildProp(
501 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700502 if source_order_val:
503 source_order = source_order_val.split(",")
504 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700505 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700506
507 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700508 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700509 raise ExternalError(
510 "Invalid ro.product.property_source_order '{}'".format(source_order))
511
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700513 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514 "ro.product", "ro.product.{}".format(source_partition), 1)
515 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700516 if prop_val:
517 return prop_val
518
519 raise ExternalError("couldn't resolve {}".format(prop))
520
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 def _GetRoProductPropsDefaultSourceOrder(self):
522 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
523 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000524 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700525 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000526 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700527 if android_version == "10":
528 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
529 # NOTE: float() conversion of android_version will have rounding error.
530 # We are checking for "9" or less, and using "< 10" is well outside of
531 # possible floating point rounding.
532 try:
533 android_version_val = float(android_version)
534 except ValueError:
535 android_version_val = 0
536 if android_version_val < 10:
537 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
538 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
539
Tianjieb37c5be2020-10-15 21:27:10 -0700540 def _GetPlatformVersion(self):
541 version_sdk = self.GetBuildProp("ro.build.version.sdk")
542 # init code switches to version_release_or_codename (see b/158483506). After
543 # API finalization, release_or_codename will be the same as release. This
544 # is the best effort to support pre-S dev stage builds.
545 if int(version_sdk) >= 30:
546 try:
547 return self.GetBuildProp("ro.build.version.release_or_codename")
548 except ExternalError:
549 logger.warning('Failed to find ro.build.version.release_or_codename')
550
551 return self.GetBuildProp("ro.build.version.release")
552
553 def _GetPartitionPlatformVersion(self, partition):
554 try:
555 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
556 partition)
557 except ExternalError:
558 return self.GetPartitionBuildProp("ro.build.version.release",
559 partition)
560
Tao Bao1c320f82019-10-04 23:25:12 -0700561 def GetOemProperty(self, key):
562 if self.oem_props is not None and key in self.oem_props:
563 return self.oem_dicts[0][key]
564 return self.GetBuildProp(key)
565
Daniel Normand5fe8622020-01-08 17:01:11 -0800566 def GetPartitionFingerprint(self, partition):
567 return self._partition_fingerprints.get(partition, None)
568
569 def CalculatePartitionFingerprint(self, partition):
570 try:
571 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
572 except ExternalError:
573 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
574 self.GetPartitionBuildProp("ro.product.brand", partition),
575 self.GetPartitionBuildProp("ro.product.name", partition),
576 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700577 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800578 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400579 self.GetPartitionBuildProp(
580 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800581 self.GetPartitionBuildProp("ro.build.type", partition),
582 self.GetPartitionBuildProp("ro.build.tags", partition))
583
Tao Bao1c320f82019-10-04 23:25:12 -0700584 def CalculateFingerprint(self):
585 if self.oem_props is None:
586 try:
587 return self.GetBuildProp("ro.build.fingerprint")
588 except ExternalError:
589 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
590 self.GetBuildProp("ro.product.brand"),
591 self.GetBuildProp("ro.product.name"),
592 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700593 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700594 self.GetBuildProp("ro.build.id"),
595 self.GetBuildProp("ro.build.version.incremental"),
596 self.GetBuildProp("ro.build.type"),
597 self.GetBuildProp("ro.build.tags"))
598 return "%s/%s/%s:%s" % (
599 self.GetOemProperty("ro.product.brand"),
600 self.GetOemProperty("ro.product.name"),
601 self.GetOemProperty("ro.product.device"),
602 self.GetBuildProp("ro.build.thumbprint"))
603
604 def WriteMountOemScript(self, script):
605 assert self.oem_props is not None
606 recovery_mount_options = self.info_dict.get("recovery_mount_options")
607 script.Mount("/oem", recovery_mount_options)
608
609 def WriteDeviceAssertions(self, script, oem_no_mount):
610 # Read the property directly if not using OEM properties.
611 if not self.oem_props:
612 script.AssertDevice(self.device)
613 return
614
615 # Otherwise assert OEM properties.
616 if not self.oem_dicts:
617 raise ExternalError(
618 "No OEM file provided to answer expected assertions")
619
620 for prop in self.oem_props.split():
621 values = []
622 for oem_dict in self.oem_dicts:
623 if prop in oem_dict:
624 values.append(oem_dict[prop])
625 if not values:
626 raise ExternalError(
627 "The OEM file is missing the property %s" % (prop,))
628 script.AssertOemProperty(prop, values, oem_no_mount)
629
630
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000631def ReadFromInputFile(input_file, fn):
632 """Reads the contents of fn from input zipfile or directory."""
633 if isinstance(input_file, zipfile.ZipFile):
634 return input_file.read(fn).decode()
635 else:
636 path = os.path.join(input_file, *fn.split("/"))
637 try:
638 with open(path) as f:
639 return f.read()
640 except IOError as e:
641 if e.errno == errno.ENOENT:
642 raise KeyError(fn)
643
644
Yifan Hong10482a22021-01-07 14:38:41 -0800645def ExtractFromInputFile(input_file, fn):
646 """Extracts the contents of fn from input zipfile or directory into a file."""
647 if isinstance(input_file, zipfile.ZipFile):
648 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500649 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800650 f.write(input_file.read(fn))
651 return tmp_file
652 else:
653 file = os.path.join(input_file, *fn.split("/"))
654 if not os.path.exists(file):
655 raise KeyError(fn)
656 return file
657
658
Tao Bao410ad8b2018-08-24 12:08:38 -0700659def LoadInfoDict(input_file, repacking=False):
660 """Loads the key/value pairs from the given input target_files.
661
Tianjiea85bdf02020-07-29 11:56:19 -0700662 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700663 checks and returns the parsed key/value pairs for to the given build. It's
664 usually called early when working on input target_files files, e.g. when
665 generating OTAs, or signing builds. Note that the function may be called
666 against an old target_files file (i.e. from past dessert releases). So the
667 property parsing needs to be backward compatible.
668
669 In a `META/misc_info.txt`, a few properties are stored as links to the files
670 in the PRODUCT_OUT directory. It works fine with the build system. However,
671 they are no longer available when (re)generating images from target_files zip.
672 When `repacking` is True, redirect these properties to the actual files in the
673 unzipped directory.
674
675 Args:
676 input_file: The input target_files file, which could be an open
677 zipfile.ZipFile instance, or a str for the dir that contains the files
678 unzipped from a target_files file.
679 repacking: Whether it's trying repack an target_files file after loading the
680 info dict (default: False). If so, it will rewrite a few loaded
681 properties (e.g. selinux_fc, root_dir) to point to the actual files in
682 target_files file. When doing repacking, `input_file` must be a dir.
683
684 Returns:
685 A dict that contains the parsed key/value pairs.
686
687 Raises:
688 AssertionError: On invalid input arguments.
689 ValueError: On malformed input values.
690 """
691 if repacking:
692 assert isinstance(input_file, str), \
693 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700694
Doug Zongkerc9253822014-02-04 12:17:58 -0800695 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000696 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800697
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700698 try:
Michael Runge6e836112014-04-15 17:40:21 -0700699 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700700 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700701 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700702
Tao Bao410ad8b2018-08-24 12:08:38 -0700703 if "recovery_api_version" not in d:
704 raise ValueError("Failed to find 'recovery_api_version'")
705 if "fstab_version" not in d:
706 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800707
Tao Bao410ad8b2018-08-24 12:08:38 -0700708 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700709 # "selinux_fc" properties should point to the file_contexts files
710 # (file_contexts.bin) under META/.
711 for key in d:
712 if key.endswith("selinux_fc"):
713 fc_basename = os.path.basename(d[key])
714 fc_config = os.path.join(input_file, "META", fc_basename)
715 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700716
Daniel Norman72c626f2019-05-13 15:58:14 -0700717 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700718
Tom Cherryd14b8952018-08-09 14:26:00 -0700719 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700720 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700721 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700722 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700723
David Anderson0ec64ac2019-12-06 12:21:18 -0800724 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700725 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700726 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800727 key_name = part_name + "_base_fs_file"
728 if key_name not in d:
729 continue
730 basename = os.path.basename(d[key_name])
731 base_fs_file = os.path.join(input_file, "META", basename)
732 if os.path.exists(base_fs_file):
733 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700734 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700735 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800736 "Failed to find %s base fs file: %s", part_name, base_fs_file)
737 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700738
Doug Zongker37974732010-09-16 17:44:38 -0700739 def makeint(key):
740 if key in d:
741 d[key] = int(d[key], 0)
742
743 makeint("recovery_api_version")
744 makeint("blocksize")
745 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700746 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700747 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700748 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700749 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800750 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700751
Steve Muckle903a1ca2020-05-07 17:32:10 -0700752 boot_images = "boot.img"
753 if "boot_images" in d:
754 boot_images = d["boot_images"]
755 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400756 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700757
Tao Bao765668f2019-10-04 22:03:00 -0700758 # Load recovery fstab if applicable.
759 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800760
Tianjie Xu861f4132018-09-12 11:49:33 -0700761 # Tries to load the build props for all partitions with care_map, including
762 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800763 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800764 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000765 d[partition_prop] = PartitionBuildProps.FromInputFile(
766 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700767 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800768
Tao Bao3ed35d32019-10-07 20:48:48 -0700769 # Set up the salt (based on fingerprint) that will be used when adding AVB
770 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800771 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700772 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800773 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800774 fingerprint = build_info.GetPartitionFingerprint(partition)
775 if fingerprint:
Kelvin Zhangc184fa12021-03-22 15:38:38 -0400776 d["avb_{}_salt".format(partition)] = sha256(
777 fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400778 try:
779 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
780 except KeyError:
781 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700782 return d
783
Tao Baod1de6f32017-03-01 16:38:48 -0800784
Daniel Norman4cc9df62019-07-18 10:11:07 -0700785def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900786 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700787 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900788
Daniel Norman4cc9df62019-07-18 10:11:07 -0700789
790def LoadDictionaryFromFile(file_path):
791 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900792 return LoadDictionaryFromLines(lines)
793
794
Michael Runge6e836112014-04-15 17:40:21 -0700795def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700796 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700797 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700798 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700799 if not line or line.startswith("#"):
800 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700801 if "=" in line:
802 name, value = line.split("=", 1)
803 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700804 return d
805
Tao Baod1de6f32017-03-01 16:38:48 -0800806
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000807class PartitionBuildProps(object):
808 """The class holds the build prop of a particular partition.
809
810 This class loads the build.prop and holds the build properties for a given
811 partition. It also partially recognizes the 'import' statement in the
812 build.prop; and calculates alternative values of some specific build
813 properties during runtime.
814
815 Attributes:
816 input_file: a zipped target-file or an unzipped target-file directory.
817 partition: name of the partition.
818 props_allow_override: a list of build properties to search for the
819 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000820 build_props: a dict of build properties for the given partition.
821 prop_overrides: a set of props that are overridden by import.
822 placeholder_values: A dict of runtime variables' values to replace the
823 placeholders in the build.prop file. We expect exactly one value for
824 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000825 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400826
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000828 self.input_file = input_file
829 self.partition = name
830 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000831 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000832 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000833 self.prop_overrides = set()
834 self.placeholder_values = {}
835 if placeholder_values:
836 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000837
838 @staticmethod
839 def FromDictionary(name, build_props):
840 """Constructs an instance from a build prop dictionary."""
841
842 props = PartitionBuildProps("unknown", name)
843 props.build_props = build_props.copy()
844 return props
845
846 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000847 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000848 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800849
850 if name == "boot":
851 data = PartitionBuildProps._ReadBootPropFile(input_file)
852 else:
853 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
854
855 props = PartitionBuildProps(input_file, name, placeholder_values)
856 props._LoadBuildProp(data)
857 return props
858
859 @staticmethod
860 def _ReadBootPropFile(input_file):
861 """
862 Read build.prop for boot image from input_file.
863 Return empty string if not found.
864 """
865 try:
866 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
867 except KeyError:
868 logger.warning('Failed to read IMAGES/boot.img')
869 return ''
870 prop_file = GetBootImageBuildProp(boot_img)
871 if prop_file is None:
872 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500873 with open(prop_file, "r") as f:
874 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800875
876 @staticmethod
877 def _ReadPartitionPropFile(input_file, name):
878 """
879 Read build.prop for name from input_file.
880 Return empty string if not found.
881 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000882 data = ''
883 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
884 '{}/build.prop'.format(name.upper())]:
885 try:
886 data = ReadFromInputFile(input_file, prop_file)
887 break
888 except KeyError:
889 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800890 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000891
Yifan Hong125d0b62020-09-24 17:07:03 -0700892 @staticmethod
893 def FromBuildPropFile(name, build_prop_file):
894 """Constructs an instance from a build prop file."""
895
896 props = PartitionBuildProps("unknown", name)
897 with open(build_prop_file) as f:
898 props._LoadBuildProp(f.read())
899 return props
900
Tianjie Xu9afb2212020-05-10 21:48:15 +0000901 def _LoadBuildProp(self, data):
902 for line in data.split('\n'):
903 line = line.strip()
904 if not line or line.startswith("#"):
905 continue
906 if line.startswith("import"):
907 overrides = self._ImportParser(line)
908 duplicates = self.prop_overrides.intersection(overrides.keys())
909 if duplicates:
910 raise ValueError('prop {} is overridden multiple times'.format(
911 ','.join(duplicates)))
912 self.prop_overrides = self.prop_overrides.union(overrides.keys())
913 self.build_props.update(overrides)
914 elif "=" in line:
915 name, value = line.split("=", 1)
916 if name in self.prop_overrides:
917 raise ValueError('prop {} is set again after overridden by import '
918 'statement'.format(name))
919 self.build_props[name] = value
920
921 def _ImportParser(self, line):
922 """Parses the build prop in a given import statement."""
923
924 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400925 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700927
928 if len(tokens) == 3:
929 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
930 return {}
931
Tianjie Xu9afb2212020-05-10 21:48:15 +0000932 import_path = tokens[1]
933 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
934 raise ValueError('Unrecognized import path {}'.format(line))
935
936 # We only recognize a subset of import statement that the init process
937 # supports. And we can loose the restriction based on how the dynamic
938 # fingerprint is used in practice. The placeholder format should be
939 # ${placeholder}, and its value should be provided by the caller through
940 # the placeholder_values.
941 for prop, value in self.placeholder_values.items():
942 prop_place_holder = '${{{}}}'.format(prop)
943 if prop_place_holder in import_path:
944 import_path = import_path.replace(prop_place_holder, value)
945 if '$' in import_path:
946 logger.info('Unresolved place holder in import path %s', import_path)
947 return {}
948
949 import_path = import_path.replace('/{}'.format(self.partition),
950 self.partition.upper())
951 logger.info('Parsing build props override from %s', import_path)
952
953 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
954 d = LoadDictionaryFromLines(lines)
955 return {key: val for key, val in d.items()
956 if key in self.props_allow_override}
957
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000958 def GetProp(self, prop):
959 return self.build_props.get(prop)
960
961
Tianjie Xucfa86222016-03-07 16:31:19 -0800962def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
963 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700964 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700965 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700966 self.mount_point = mount_point
967 self.fs_type = fs_type
968 self.device = device
969 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700970 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700971 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700972
973 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800974 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700975 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700976 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700977 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700978
Tao Baod1de6f32017-03-01 16:38:48 -0800979 assert fstab_version == 2
980
981 d = {}
982 for line in data.split("\n"):
983 line = line.strip()
984 if not line or line.startswith("#"):
985 continue
986
987 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
988 pieces = line.split()
989 if len(pieces) != 5:
990 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
991
992 # Ignore entries that are managed by vold.
993 options = pieces[4]
994 if "voldmanaged=" in options:
995 continue
996
997 # It's a good line, parse it.
998 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700999 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001000 options = options.split(",")
1001 for i in options:
1002 if i.startswith("length="):
1003 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001004 elif i == "slotselect":
1005 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001006 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001007 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001009
Tao Baod1de6f32017-03-01 16:38:48 -08001010 mount_flags = pieces[3]
1011 # Honor the SELinux context if present.
1012 context = None
1013 for i in mount_flags.split(","):
1014 if i.startswith("context="):
1015 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001016
Tao Baod1de6f32017-03-01 16:38:48 -08001017 mount_point = pieces[1]
1018 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001019 device=pieces[0], length=length, context=context,
1020 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001021
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001022 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001023 # system. Other areas assume system is always at "/system" so point /system
1024 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001025 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001026 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001027 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001028 return d
1029
1030
Tao Bao765668f2019-10-04 22:03:00 -07001031def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1032 """Finds the path to recovery fstab and loads its contents."""
1033 # recovery fstab is only meaningful when installing an update via recovery
1034 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001035 if info_dict.get('ab_update') == 'true' and \
1036 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001037 return None
1038
1039 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1040 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1041 # cases, since it may load the info_dict from an old build (e.g. when
1042 # generating incremental OTAs from that build).
1043 system_root_image = info_dict.get('system_root_image') == 'true'
1044 if info_dict.get('no_recovery') != 'true':
1045 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1046 if isinstance(input_file, zipfile.ZipFile):
1047 if recovery_fstab_path not in input_file.namelist():
1048 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1049 else:
1050 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1051 if not os.path.exists(path):
1052 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1053 return LoadRecoveryFSTab(
1054 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1055 system_root_image)
1056
1057 if info_dict.get('recovery_as_boot') == 'true':
1058 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1059 if isinstance(input_file, zipfile.ZipFile):
1060 if recovery_fstab_path not in input_file.namelist():
1061 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1062 else:
1063 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1064 if not os.path.exists(path):
1065 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1066 return LoadRecoveryFSTab(
1067 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1068 system_root_image)
1069
1070 return None
1071
1072
Doug Zongker37974732010-09-16 17:44:38 -07001073def DumpInfoDict(d):
1074 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001075 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001076
Dan Albert8b72aef2015-03-23 19:13:21 -07001077
Daniel Norman55417142019-11-25 16:04:36 -08001078def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001079 """Merges dynamic partition info variables.
1080
1081 Args:
1082 framework_dict: The dictionary of dynamic partition info variables from the
1083 partial framework target files.
1084 vendor_dict: The dictionary of dynamic partition info variables from the
1085 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001086
1087 Returns:
1088 The merged dynamic partition info dictionary.
1089 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001090
1091 def uniq_concat(a, b):
1092 combined = set(a.split(" "))
1093 combined.update(set(b.split(" ")))
1094 combined = [item.strip() for item in combined if item.strip()]
1095 return " ".join(sorted(combined))
1096
1097 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001098 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001099 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1100
1101 merged_dict = {"use_dynamic_partitions": "true"}
1102
1103 merged_dict["dynamic_partition_list"] = uniq_concat(
1104 framework_dict.get("dynamic_partition_list", ""),
1105 vendor_dict.get("dynamic_partition_list", ""))
1106
1107 # Super block devices are defined by the vendor dict.
1108 if "super_block_devices" in vendor_dict:
1109 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1110 for block_device in merged_dict["super_block_devices"].split(" "):
1111 key = "super_%s_device_size" % block_device
1112 if key not in vendor_dict:
1113 raise ValueError("Vendor dict does not contain required key %s." % key)
1114 merged_dict[key] = vendor_dict[key]
1115
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001116 # Partition groups and group sizes are defined by the vendor dict because
1117 # these values may vary for each board that uses a shared system image.
1118 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001119 for partition_group in merged_dict["super_partition_groups"].split(" "):
1120 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001121 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001122 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
1126 # Set the partition group's partition list using a concatenation of the
1127 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001128 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001129 merged_dict[key] = uniq_concat(
1130 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301131
Daniel Normanb0c75912020-09-24 14:30:21 -07001132 # Various other flags should be copied from the vendor dict, if defined.
1133 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1134 "super_metadata_device", "super_partition_error_limit",
1135 "super_partition_size"):
1136 if key in vendor_dict.keys():
1137 merged_dict[key] = vendor_dict[key]
1138
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001139 return merged_dict
1140
1141
Daniel Norman21c34f72020-11-11 17:25:50 -08001142def PartitionMapFromTargetFiles(target_files_dir):
1143 """Builds a map from partition -> path within an extracted target files directory."""
1144 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1145 possible_subdirs = {
1146 "system": ["SYSTEM"],
1147 "vendor": ["VENDOR", "SYSTEM/vendor"],
1148 "product": ["PRODUCT", "SYSTEM/product"],
1149 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1150 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1151 "vendor_dlkm": [
1152 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1153 ],
1154 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1155 }
1156 partition_map = {}
1157 for partition, subdirs in possible_subdirs.items():
1158 for subdir in subdirs:
1159 if os.path.exists(os.path.join(target_files_dir, subdir)):
1160 partition_map[partition] = subdir
1161 break
1162 return partition_map
1163
1164
Daniel Normand3351562020-10-29 12:33:11 -07001165def SharedUidPartitionViolations(uid_dict, partition_groups):
1166 """Checks for APK sharedUserIds that cross partition group boundaries.
1167
1168 This uses a single or merged build's shareduid_violation_modules.json
1169 output file, as generated by find_shareduid_violation.py or
1170 core/tasks/find-shareduid-violation.mk.
1171
1172 An error is defined as a sharedUserId that is found in a set of partitions
1173 that span more than one partition group.
1174
1175 Args:
1176 uid_dict: A dictionary created by using the standard json module to read a
1177 complete shareduid_violation_modules.json file.
1178 partition_groups: A list of groups, where each group is a list of
1179 partitions.
1180
1181 Returns:
1182 A list of error messages.
1183 """
1184 errors = []
1185 for uid, partitions in uid_dict.items():
1186 found_in_groups = [
1187 group for group in partition_groups
1188 if set(partitions.keys()) & set(group)
1189 ]
1190 if len(found_in_groups) > 1:
1191 errors.append(
1192 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1193 % (uid, ",".join(sorted(partitions.keys()))))
1194 return errors
1195
1196
Daniel Norman21c34f72020-11-11 17:25:50 -08001197def RunHostInitVerifier(product_out, partition_map):
1198 """Runs host_init_verifier on the init rc files within partitions.
1199
1200 host_init_verifier searches the etc/init path within each partition.
1201
1202 Args:
1203 product_out: PRODUCT_OUT directory, containing partition directories.
1204 partition_map: A map of partition name -> relative path within product_out.
1205 """
1206 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1207 cmd = ["host_init_verifier"]
1208 for partition, path in partition_map.items():
1209 if partition not in allowed_partitions:
1210 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1211 partition)
1212 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1213 # Add --property-contexts if the file exists on the partition.
1214 property_contexts = "%s_property_contexts" % (
1215 "plat" if partition == "system" else partition)
1216 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1217 property_contexts)
1218 if os.path.exists(property_contexts_path):
1219 cmd.append("--property-contexts=%s" % property_contexts_path)
1220 # Add the passwd file if the file exists on the partition.
1221 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1222 if os.path.exists(passwd_path):
1223 cmd.extend(["-p", passwd_path])
1224 return RunAndCheckOutput(cmd)
1225
1226
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001227def AppendAVBSigningArgs(cmd, partition):
1228 """Append signing arguments for avbtool."""
1229 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1230 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001231 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1232 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1233 if os.path.exists(new_key_path):
1234 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001235 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1236 if key_path and algorithm:
1237 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001238 avb_salt = OPTIONS.info_dict.get("avb_salt")
1239 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001240 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001241 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001242
1243
Tao Bao765668f2019-10-04 22:03:00 -07001244def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001245 """Returns the VBMeta arguments for partition.
1246
1247 It sets up the VBMeta argument by including the partition descriptor from the
1248 given 'image', or by configuring the partition as a chained partition.
1249
1250 Args:
1251 partition: The name of the partition (e.g. "system").
1252 image: The path to the partition image.
1253 info_dict: A dict returned by common.LoadInfoDict(). Will use
1254 OPTIONS.info_dict if None has been given.
1255
1256 Returns:
1257 A list of VBMeta arguments.
1258 """
1259 if info_dict is None:
1260 info_dict = OPTIONS.info_dict
1261
1262 # Check if chain partition is used.
1263 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001264 if not key_path:
1265 return ["--include_descriptors_from_image", image]
1266
1267 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1268 # into vbmeta.img. The recovery image will be configured on an independent
1269 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1270 # See details at
1271 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001272 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001273 return []
1274
1275 # Otherwise chain the partition into vbmeta.
1276 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1277 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001278
1279
Tao Bao02a08592018-07-22 12:40:45 -07001280def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1281 """Constructs and returns the arg to build or verify a chained partition.
1282
1283 Args:
1284 partition: The partition name.
1285 info_dict: The info dict to look up the key info and rollback index
1286 location.
1287 key: The key to be used for building or verifying the partition. Defaults to
1288 the key listed in info_dict.
1289
1290 Returns:
1291 A string of form "partition:rollback_index_location:key" that can be used to
1292 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001293 """
1294 if key is None:
1295 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001296 if key and not os.path.exists(key) and OPTIONS.search_path:
1297 new_key_path = os.path.join(OPTIONS.search_path, key)
1298 if os.path.exists(new_key_path):
1299 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001300 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001301 rollback_index_location = info_dict[
1302 "avb_" + partition + "_rollback_index_location"]
1303 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1304
1305
Tianjie20dd8f22020-04-19 15:51:16 -07001306def ConstructAftlMakeImageCommands(output_image):
1307 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001308
1309 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001310 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001311 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1312 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1313 'No AFTL manufacturer key provided.'
1314
1315 vbmeta_image = MakeTempFile()
1316 os.rename(output_image, vbmeta_image)
1317 build_info = BuildInfo(OPTIONS.info_dict)
1318 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001319 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001320 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001321 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001322 "--vbmeta_image_path", vbmeta_image,
1323 "--output", output_image,
1324 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001325 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001326 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1327 "--algorithm", "SHA256_RSA4096",
1328 "--padding", "4096"]
1329 if OPTIONS.aftl_signer_helper:
1330 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001331 return aftl_cmd
1332
1333
1334def AddAftlInclusionProof(output_image):
1335 """Appends the aftl inclusion proof to the vbmeta image."""
1336
1337 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001338 RunAndCheckOutput(aftl_cmd)
1339
1340 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1341 output_image, '--transparency_log_pub_keys',
1342 OPTIONS.aftl_key_path]
1343 RunAndCheckOutput(verify_cmd)
1344
1345
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001346def AppendGkiSigningArgs(cmd):
1347 """Append GKI signing arguments for mkbootimg."""
1348 # e.g., --gki_signing_key path/to/signing_key
1349 # --gki_signing_algorithm SHA256_RSA4096"
1350
1351 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1352 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1353 if not key_path:
1354 return
1355
1356 if not os.path.exists(key_path) and OPTIONS.search_path:
1357 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1358 if os.path.exists(new_key_path):
1359 key_path = new_key_path
1360
1361 # Checks key_path exists, before appending --gki_signing_* args.
1362 if not os.path.exists(key_path):
1363 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1364
1365 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1366 if key_path and algorithm:
1367 cmd.extend(["--gki_signing_key", key_path,
1368 "--gki_signing_algorithm", algorithm])
1369
1370 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1371 if signature_args:
1372 cmd.extend(["--gki_signing_signature_args", signature_args])
1373
1374
Daniel Norman276f0622019-07-26 14:13:51 -07001375def BuildVBMeta(image_path, partitions, name, needed_partitions):
1376 """Creates a VBMeta image.
1377
1378 It generates the requested VBMeta image. The requested image could be for
1379 top-level or chained VBMeta image, which is determined based on the name.
1380
1381 Args:
1382 image_path: The output path for the new VBMeta image.
1383 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001384 values. Only valid partition names are accepted, as partitions listed
1385 in common.AVB_PARTITIONS and custom partitions listed in
1386 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001387 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1388 needed_partitions: Partitions whose descriptors should be included into the
1389 generated VBMeta image.
1390
1391 Raises:
1392 AssertionError: On invalid input args.
1393 """
1394 avbtool = OPTIONS.info_dict["avb_avbtool"]
1395 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1396 AppendAVBSigningArgs(cmd, name)
1397
Hongguang Chenf23364d2020-04-27 18:36:36 -07001398 custom_partitions = OPTIONS.info_dict.get(
1399 "avb_custom_images_partition_list", "").strip().split()
1400
Daniel Norman276f0622019-07-26 14:13:51 -07001401 for partition, path in partitions.items():
1402 if partition not in needed_partitions:
1403 continue
1404 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001405 partition in AVB_VBMETA_PARTITIONS or
1406 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001407 'Unknown partition: {}'.format(partition)
1408 assert os.path.exists(path), \
1409 'Failed to find {} for {}'.format(path, partition)
1410 cmd.extend(GetAvbPartitionArg(partition, path))
1411
1412 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1413 if args and args.strip():
1414 split_args = shlex.split(args)
1415 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001416 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001417 # as a path relative to source tree, which may not be available at the
1418 # same location when running this script (we have the input target_files
1419 # zip only). For such cases, we additionally scan other locations (e.g.
1420 # IMAGES/, RADIO/, etc) before bailing out.
1421 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001422 chained_image = split_args[index + 1]
1423 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001424 continue
1425 found = False
1426 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1427 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001428 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001429 if os.path.exists(alt_path):
1430 split_args[index + 1] = alt_path
1431 found = True
1432 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001433 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001434 cmd.extend(split_args)
1435
1436 RunAndCheckOutput(cmd)
1437
Tianjie Xueaed60c2020-03-12 00:33:28 -07001438 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001439 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001440 AddAftlInclusionProof(image_path)
1441
Daniel Norman276f0622019-07-26 14:13:51 -07001442
J. Avila98cd4cc2020-06-10 20:09:10 +00001443def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001444 ramdisk_img = tempfile.NamedTemporaryFile()
1445
1446 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1447 cmd = ["mkbootfs", "-f", fs_config_file,
1448 os.path.join(sourcedir, "RAMDISK")]
1449 else:
1450 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1451 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001452 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001453 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001454 stdout=ramdisk_img.file.fileno())
1455 else:
1456 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001457
1458 p2.wait()
1459 p1.wait()
1460 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001461 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001462
1463 return ramdisk_img
1464
1465
Steve Muckle9793cf62020-04-08 18:27:00 -07001466def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001467 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001468 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001469
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001470 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001471 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1472 we are building a two-step special image (i.e. building a recovery image to
1473 be loaded into /boot in two-step OTAs).
1474
1475 Return the image data, or None if sourcedir does not appear to contains files
1476 for building the requested image.
1477 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001478
Yifan Hong63c5ca12020-10-08 11:54:02 -07001479 if info_dict is None:
1480 info_dict = OPTIONS.info_dict
1481
Steve Muckle9793cf62020-04-08 18:27:00 -07001482 # "boot" or "recovery", without extension.
1483 partition_name = os.path.basename(sourcedir).lower()
1484
Yifan Hong63c5ca12020-10-08 11:54:02 -07001485 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001486 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001487 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1488 logger.info("Excluded kernel binary from recovery image.")
1489 else:
1490 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001491 else:
1492 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001493 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001494 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001495 return None
1496
1497 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001498 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001499
Doug Zongkereef39442009-04-02 12:14:19 -07001500 img = tempfile.NamedTemporaryFile()
1501
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001502 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001503 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1504 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001505
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001506 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1507 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1508
Yifan Hong63c5ca12020-10-08 11:54:02 -07001509 cmd = [mkbootimg]
1510 if kernel:
1511 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001512
Benoit Fradina45a8682014-07-14 21:00:43 +02001513 fn = os.path.join(sourcedir, "second")
1514 if os.access(fn, os.F_OK):
1515 cmd.append("--second")
1516 cmd.append(fn)
1517
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001518 fn = os.path.join(sourcedir, "dtb")
1519 if os.access(fn, os.F_OK):
1520 cmd.append("--dtb")
1521 cmd.append(fn)
1522
Doug Zongker171f1cd2009-06-15 22:36:37 -07001523 fn = os.path.join(sourcedir, "cmdline")
1524 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001525 cmd.append("--cmdline")
1526 cmd.append(open(fn).read().rstrip("\n"))
1527
1528 fn = os.path.join(sourcedir, "base")
1529 if os.access(fn, os.F_OK):
1530 cmd.append("--base")
1531 cmd.append(open(fn).read().rstrip("\n"))
1532
Ying Wang4de6b5b2010-08-25 14:29:34 -07001533 fn = os.path.join(sourcedir, "pagesize")
1534 if os.access(fn, os.F_OK):
1535 cmd.append("--pagesize")
1536 cmd.append(open(fn).read().rstrip("\n"))
1537
Steve Mucklef84668e2020-03-16 19:13:46 -07001538 if partition_name == "recovery":
1539 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301540 if not args:
1541 # Fall back to "mkbootimg_args" for recovery image
1542 # in case "recovery_mkbootimg_args" is not set.
1543 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001544 else:
1545 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001546 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001547 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001548
Tao Bao76def242017-11-21 09:25:31 -08001549 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001550 if args and args.strip():
1551 cmd.extend(shlex.split(args))
1552
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001553 if has_ramdisk:
1554 cmd.extend(["--ramdisk", ramdisk_img.name])
1555
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001556 AppendGkiSigningArgs(cmd)
1557
Tao Baod95e9fd2015-03-29 23:07:41 -07001558 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001559 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001560 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001561 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001562 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001563 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001564
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001565 if partition_name == "recovery":
1566 if info_dict.get("include_recovery_dtbo") == "true":
1567 fn = os.path.join(sourcedir, "recovery_dtbo")
1568 cmd.extend(["--recovery_dtbo", fn])
1569 if info_dict.get("include_recovery_acpio") == "true":
1570 fn = os.path.join(sourcedir, "recovery_acpio")
1571 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001572
Tao Bao986ee862018-10-04 15:46:16 -07001573 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001574
Tao Bao76def242017-11-21 09:25:31 -08001575 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001576 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001577 # Hard-code the path as "/boot" for two-step special recovery image (which
1578 # will be loaded into /boot during the two-step OTA).
1579 if two_step_image:
1580 path = "/boot"
1581 else:
Tao Baobf70c312017-07-11 17:27:55 -07001582 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001583 cmd = [OPTIONS.boot_signer_path]
1584 cmd.extend(OPTIONS.boot_signer_args)
1585 cmd.extend([path, img.name,
1586 info_dict["verity_key"] + ".pk8",
1587 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001588 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001589
Tao Baod95e9fd2015-03-29 23:07:41 -07001590 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001591 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001592 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001593 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001594 # We have switched from the prebuilt futility binary to using the tool
1595 # (futility-host) built from the source. Override the setting in the old
1596 # TF.zip.
1597 futility = info_dict["futility"]
1598 if futility.startswith("prebuilts/"):
1599 futility = "futility-host"
1600 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001601 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001602 info_dict["vboot_key"] + ".vbprivk",
1603 info_dict["vboot_subkey"] + ".vbprivk",
1604 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001605 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001606 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001607
Tao Baof3282b42015-04-01 11:21:55 -07001608 # Clean up the temp files.
1609 img_unsigned.close()
1610 img_keyblock.close()
1611
David Zeuthen8fecb282017-12-01 16:24:01 -05001612 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001613 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001614 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001615 if partition_name == "recovery":
1616 part_size = info_dict["recovery_size"]
1617 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001618 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001619 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001620 "--partition_size", str(part_size), "--partition_name",
1621 partition_name]
1622 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001623 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001624 if args and args.strip():
1625 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001626 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001627
1628 img.seek(os.SEEK_SET, 0)
1629 data = img.read()
1630
1631 if has_ramdisk:
1632 ramdisk_img.close()
1633 img.close()
1634
1635 return data
1636
1637
Doug Zongkerd5131602012-08-02 14:46:42 -07001638def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001639 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001640 """Return a File object with the desired bootable image.
1641
1642 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1643 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1644 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001645
Doug Zongker55d93282011-01-25 17:03:34 -08001646 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1647 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001648 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001649 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001650
1651 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1652 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001653 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001654 return File.FromLocalFile(name, prebuilt_path)
1655
Tao Bao32fcdab2018-10-12 10:30:39 -07001656 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001657
1658 if info_dict is None:
1659 info_dict = OPTIONS.info_dict
1660
1661 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001662 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1663 # for recovery.
1664 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1665 prebuilt_name != "boot.img" or
1666 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001667
Doug Zongker6f1d0312014-08-22 08:07:12 -07001668 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001669 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001670 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001671 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001672 if data:
1673 return File(name, data)
1674 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001675
Doug Zongkereef39442009-04-02 12:14:19 -07001676
Steve Mucklee1b10862019-07-10 10:49:37 -07001677def _BuildVendorBootImage(sourcedir, info_dict=None):
1678 """Build a vendor boot image from the specified sourcedir.
1679
1680 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1681 turn them into a vendor boot image.
1682
1683 Return the image data, or None if sourcedir does not appear to contains files
1684 for building the requested image.
1685 """
1686
1687 if info_dict is None:
1688 info_dict = OPTIONS.info_dict
1689
1690 img = tempfile.NamedTemporaryFile()
1691
J. Avila98cd4cc2020-06-10 20:09:10 +00001692 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1693 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001694
1695 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1696 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1697
1698 cmd = [mkbootimg]
1699
1700 fn = os.path.join(sourcedir, "dtb")
1701 if os.access(fn, os.F_OK):
1702 cmd.append("--dtb")
1703 cmd.append(fn)
1704
1705 fn = os.path.join(sourcedir, "vendor_cmdline")
1706 if os.access(fn, os.F_OK):
1707 cmd.append("--vendor_cmdline")
1708 cmd.append(open(fn).read().rstrip("\n"))
1709
1710 fn = os.path.join(sourcedir, "base")
1711 if os.access(fn, os.F_OK):
1712 cmd.append("--base")
1713 cmd.append(open(fn).read().rstrip("\n"))
1714
1715 fn = os.path.join(sourcedir, "pagesize")
1716 if os.access(fn, os.F_OK):
1717 cmd.append("--pagesize")
1718 cmd.append(open(fn).read().rstrip("\n"))
1719
1720 args = info_dict.get("mkbootimg_args")
1721 if args and args.strip():
1722 cmd.extend(shlex.split(args))
1723
1724 args = info_dict.get("mkbootimg_version_args")
1725 if args and args.strip():
1726 cmd.extend(shlex.split(args))
1727
1728 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1729 cmd.extend(["--vendor_boot", img.name])
1730
Devin Moore50509012021-01-13 10:45:04 -08001731 fn = os.path.join(sourcedir, "vendor_bootconfig")
1732 if os.access(fn, os.F_OK):
1733 cmd.append("--vendor_bootconfig")
1734 cmd.append(fn)
1735
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001736 ramdisk_fragment_imgs = []
1737 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1738 if os.access(fn, os.F_OK):
1739 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1740 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001741 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1742 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001743 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001744 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1745 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001746 # Use prebuilt image if found, else create ramdisk from supplied files.
1747 if os.access(fn, os.F_OK):
1748 ramdisk_fragment_pathname = fn
1749 else:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04001750 ramdisk_fragment_root = os.path.join(
1751 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1752 ramdisk_fragment_img = _MakeRamdisk(
1753 ramdisk_fragment_root, lz4_ramdisks=use_lz4)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001754 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1755 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1756 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1757
Steve Mucklee1b10862019-07-10 10:49:37 -07001758 RunAndCheckOutput(cmd)
1759
1760 # AVB: if enabled, calculate and add hash.
1761 if info_dict.get("avb_enable") == "true":
1762 avbtool = info_dict["avb_avbtool"]
1763 part_size = info_dict["vendor_boot_size"]
1764 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001765 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001766 AppendAVBSigningArgs(cmd, "vendor_boot")
1767 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1768 if args and args.strip():
1769 cmd.extend(shlex.split(args))
1770 RunAndCheckOutput(cmd)
1771
1772 img.seek(os.SEEK_SET, 0)
1773 data = img.read()
1774
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001775 for f in ramdisk_fragment_imgs:
1776 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001777 ramdisk_img.close()
1778 img.close()
1779
1780 return data
1781
1782
1783def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1784 info_dict=None):
1785 """Return a File object with the desired vendor boot image.
1786
1787 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1788 the source files in 'unpack_dir'/'tree_subdir'."""
1789
1790 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1791 if os.path.exists(prebuilt_path):
1792 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1793 return File.FromLocalFile(name, prebuilt_path)
1794
1795 logger.info("building image from target_files %s...", tree_subdir)
1796
1797 if info_dict is None:
1798 info_dict = OPTIONS.info_dict
1799
Kelvin Zhang0876c412020-06-23 15:06:58 -04001800 data = _BuildVendorBootImage(
1801 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001802 if data:
1803 return File(name, data)
1804 return None
1805
1806
Narayan Kamatha07bf042017-08-14 14:49:21 +01001807def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001808 """Gunzips the given gzip compressed file to a given output file."""
1809 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001810 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001811 shutil.copyfileobj(in_file, out_file)
1812
1813
Tao Bao0ff15de2019-03-20 11:26:06 -07001814def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001815 """Unzips the archive to the given directory.
1816
1817 Args:
1818 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001819 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001820 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1821 archvie. Non-matching patterns will be filtered out. If there's no match
1822 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001823 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001824 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001825 if patterns is not None:
1826 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001827 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001828 names = input_zip.namelist()
1829 filtered = [
1830 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1831
1832 # There isn't any matching files. Don't unzip anything.
1833 if not filtered:
1834 return
1835 cmd.extend(filtered)
1836
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001837 RunAndCheckOutput(cmd)
1838
1839
Doug Zongker75f17362009-12-08 13:46:44 -08001840def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001841 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001842
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001843 Args:
1844 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1845 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1846
1847 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1848 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001849
Tao Bao1c830bf2017-12-25 10:43:47 -08001850 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001851 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001852 """
Doug Zongkereef39442009-04-02 12:14:19 -07001853
Tao Bao1c830bf2017-12-25 10:43:47 -08001854 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001855 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1856 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001857 UnzipToDir(m.group(1), tmp, pattern)
1858 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001859 filename = m.group(1)
1860 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001861 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001862
Tao Baodba59ee2018-01-09 13:21:02 -08001863 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001864
1865
Yifan Hong8a66a712019-04-04 15:37:57 -07001866def GetUserImage(which, tmpdir, input_zip,
1867 info_dict=None,
1868 allow_shared_blocks=None,
1869 hashtree_info_generator=None,
1870 reset_file_map=False):
1871 """Returns an Image object suitable for passing to BlockImageDiff.
1872
1873 This function loads the specified image from the given path. If the specified
1874 image is sparse, it also performs additional processing for OTA purpose. For
1875 example, it always adds block 0 to clobbered blocks list. It also detects
1876 files that cannot be reconstructed from the block list, for whom we should
1877 avoid applying imgdiff.
1878
1879 Args:
1880 which: The partition name.
1881 tmpdir: The directory that contains the prebuilt image and block map file.
1882 input_zip: The target-files ZIP archive.
1883 info_dict: The dict to be looked up for relevant info.
1884 allow_shared_blocks: If image is sparse, whether having shared blocks is
1885 allowed. If none, it is looked up from info_dict.
1886 hashtree_info_generator: If present and image is sparse, generates the
1887 hashtree_info for this sparse image.
1888 reset_file_map: If true and image is sparse, reset file map before returning
1889 the image.
1890 Returns:
1891 A Image object. If it is a sparse image and reset_file_map is False, the
1892 image will have file_map info loaded.
1893 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001894 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001895 info_dict = LoadInfoDict(input_zip)
1896
1897 is_sparse = info_dict.get("extfs_sparse_flag")
1898
1899 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1900 # shared blocks (i.e. some blocks will show up in multiple files' block
1901 # list). We can only allocate such shared blocks to the first "owner", and
1902 # disable imgdiff for all later occurrences.
1903 if allow_shared_blocks is None:
1904 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1905
1906 if is_sparse:
1907 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1908 hashtree_info_generator)
1909 if reset_file_map:
1910 img.ResetFileMap()
1911 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001912 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001913
1914
1915def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1916 """Returns a Image object suitable for passing to BlockImageDiff.
1917
1918 This function loads the specified non-sparse image from the given path.
1919
1920 Args:
1921 which: The partition name.
1922 tmpdir: The directory that contains the prebuilt image and block map file.
1923 Returns:
1924 A Image object.
1925 """
1926 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1927 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1928
1929 # The image and map files must have been created prior to calling
1930 # ota_from_target_files.py (since LMP).
1931 assert os.path.exists(path) and os.path.exists(mappath)
1932
Tianjie Xu41976c72019-07-03 13:57:01 -07001933 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1934
Yifan Hong8a66a712019-04-04 15:37:57 -07001935
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001936def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1937 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001938 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1939
1940 This function loads the specified sparse image from the given path, and
1941 performs additional processing for OTA purpose. For example, it always adds
1942 block 0 to clobbered blocks list. It also detects files that cannot be
1943 reconstructed from the block list, for whom we should avoid applying imgdiff.
1944
1945 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001946 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001947 tmpdir: The directory that contains the prebuilt image and block map file.
1948 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001949 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001950 hashtree_info_generator: If present, generates the hashtree_info for this
1951 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001952 Returns:
1953 A SparseImage object, with file_map info loaded.
1954 """
Tao Baoc765cca2018-01-31 17:32:40 -08001955 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1956 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1957
1958 # The image and map files must have been created prior to calling
1959 # ota_from_target_files.py (since LMP).
1960 assert os.path.exists(path) and os.path.exists(mappath)
1961
1962 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1963 # it to clobbered_blocks so that it will be written to the target
1964 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1965 clobbered_blocks = "0"
1966
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001967 image = sparse_img.SparseImage(
1968 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1969 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001970
1971 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1972 # if they contain all zeros. We can't reconstruct such a file from its block
1973 # list. Tag such entries accordingly. (Bug: 65213616)
1974 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001975 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001976 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001977 continue
1978
Tom Cherryd14b8952018-08-09 14:26:00 -07001979 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1980 # filename listed in system.map may contain an additional leading slash
1981 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1982 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001983 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001984 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001985 arcname = entry.lstrip('/')
1986 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001987 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001988 else:
1989 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07001990
1991 assert arcname in input_zip.namelist(), \
1992 "Failed to find the ZIP entry for {}".format(entry)
1993
Tao Baoc765cca2018-01-31 17:32:40 -08001994 info = input_zip.getinfo(arcname)
1995 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001996
1997 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001998 # image, check the original block list to determine its completeness. Note
1999 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002000 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002001 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002002
Tao Baoc765cca2018-01-31 17:32:40 -08002003 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2004 ranges.extra['incomplete'] = True
2005
2006 return image
2007
2008
Doug Zongkereef39442009-04-02 12:14:19 -07002009def GetKeyPasswords(keylist):
2010 """Given a list of keys, prompt the user to enter passwords for
2011 those which require them. Return a {key: password} dict. password
2012 will be None if the key has no password."""
2013
Doug Zongker8ce7c252009-05-22 13:34:54 -07002014 no_passwords = []
2015 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002016 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002017 devnull = open("/dev/null", "w+b")
2018 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002019 # We don't need a password for things that aren't really keys.
2020 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002021 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002022 continue
2023
T.R. Fullhart37e10522013-03-18 10:31:26 -07002024 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002025 "-inform", "DER", "-nocrypt"],
2026 stdin=devnull.fileno(),
2027 stdout=devnull.fileno(),
2028 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002029 p.communicate()
2030 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002031 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002032 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002033 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002034 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2035 "-inform", "DER", "-passin", "pass:"],
2036 stdin=devnull.fileno(),
2037 stdout=devnull.fileno(),
2038 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002039 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002040 if p.returncode == 0:
2041 # Encrypted key with empty string as password.
2042 key_passwords[k] = ''
2043 elif stderr.startswith('Error decrypting key'):
2044 # Definitely encrypted key.
2045 # It would have said "Error reading key" if it didn't parse correctly.
2046 need_passwords.append(k)
2047 else:
2048 # Potentially, a type of key that openssl doesn't understand.
2049 # We'll let the routines in signapk.jar handle it.
2050 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002051 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002052
T.R. Fullhart37e10522013-03-18 10:31:26 -07002053 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002054 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002055 return key_passwords
2056
2057
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002058def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002059 """Gets the minSdkVersion declared in the APK.
2060
changho.shin0f125362019-07-08 10:59:00 +09002061 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002062 This can be both a decimal number (API Level) or a codename.
2063
2064 Args:
2065 apk_name: The APK filename.
2066
2067 Returns:
2068 The parsed SDK version string.
2069
2070 Raises:
2071 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002072 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002073 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002074 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002075 stderr=subprocess.PIPE)
2076 stdoutdata, stderrdata = proc.communicate()
2077 if proc.returncode != 0:
2078 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002079 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002080 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002081
Tao Baof47bf0f2018-03-21 23:28:51 -07002082 for line in stdoutdata.split("\n"):
2083 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002084 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2085 if m:
2086 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002087 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002088
2089
2090def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002091 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002092
Tao Baof47bf0f2018-03-21 23:28:51 -07002093 If minSdkVersion is set to a codename, it is translated to a number using the
2094 provided map.
2095
2096 Args:
2097 apk_name: The APK filename.
2098
2099 Returns:
2100 The parsed SDK version number.
2101
2102 Raises:
2103 ExternalError: On failing to get the min SDK version number.
2104 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002105 version = GetMinSdkVersion(apk_name)
2106 try:
2107 return int(version)
2108 except ValueError:
2109 # Not a decimal number. Codename?
2110 if version in codename_to_api_level_map:
2111 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002112 raise ExternalError(
2113 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2114 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002115
2116
2117def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002118 codename_to_api_level_map=None, whole_file=False,
2119 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002120 """Sign the input_name zip/jar/apk, producing output_name. Use the
2121 given key and password (the latter may be None if the key does not
2122 have a password.
2123
Doug Zongker951495f2009-08-14 12:44:19 -07002124 If whole_file is true, use the "-w" option to SignApk to embed a
2125 signature that covers the whole file in the archive comment of the
2126 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002127
2128 min_api_level is the API Level (int) of the oldest platform this file may end
2129 up on. If not specified for an APK, the API Level is obtained by interpreting
2130 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2131
2132 codename_to_api_level_map is needed to translate the codename which may be
2133 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002134
2135 Caller may optionally specify extra args to be passed to SignApk, which
2136 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002137 """
Tao Bao76def242017-11-21 09:25:31 -08002138 if codename_to_api_level_map is None:
2139 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002140 if extra_signapk_args is None:
2141 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002142
Alex Klyubin9667b182015-12-10 13:38:50 -08002143 java_library_path = os.path.join(
2144 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2145
Tao Baoe95540e2016-11-08 12:08:53 -08002146 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2147 ["-Djava.library.path=" + java_library_path,
2148 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002149 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002150 if whole_file:
2151 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002152
2153 min_sdk_version = min_api_level
2154 if min_sdk_version is None:
2155 if not whole_file:
2156 min_sdk_version = GetMinSdkVersionInt(
2157 input_name, codename_to_api_level_map)
2158 if min_sdk_version is not None:
2159 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2160
T.R. Fullhart37e10522013-03-18 10:31:26 -07002161 cmd.extend([key + OPTIONS.public_key_suffix,
2162 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002163 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002164
Tao Bao73dd4f42018-10-04 16:25:33 -07002165 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002166 if password is not None:
2167 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002168 stdoutdata, _ = proc.communicate(password)
2169 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002170 raise ExternalError(
2171 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002172 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002173
Doug Zongkereef39442009-04-02 12:14:19 -07002174
Doug Zongker37974732010-09-16 17:44:38 -07002175def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002176 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002177
Tao Bao9dd909e2017-11-14 11:27:32 -08002178 For non-AVB images, raise exception if the data is too big. Print a warning
2179 if the data is nearing the maximum size.
2180
2181 For AVB images, the actual image size should be identical to the limit.
2182
2183 Args:
2184 data: A string that contains all the data for the partition.
2185 target: The partition name. The ".img" suffix is optional.
2186 info_dict: The dict to be looked up for relevant info.
2187 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002188 if target.endswith(".img"):
2189 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002190 mount_point = "/" + target
2191
Ying Wangf8824af2014-06-03 14:07:27 -07002192 fs_type = None
2193 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002194 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002195 if mount_point == "/userdata":
2196 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002197 p = info_dict["fstab"][mount_point]
2198 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002199 device = p.device
2200 if "/" in device:
2201 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002202 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002203 if not fs_type or not limit:
2204 return
Doug Zongkereef39442009-04-02 12:14:19 -07002205
Andrew Boie0f9aec82012-02-14 09:32:52 -08002206 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002207 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2208 # path.
2209 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2210 if size != limit:
2211 raise ExternalError(
2212 "Mismatching image size for %s: expected %d actual %d" % (
2213 target, limit, size))
2214 else:
2215 pct = float(size) * 100.0 / limit
2216 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2217 if pct >= 99.0:
2218 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002219
2220 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002221 logger.warning("\n WARNING: %s\n", msg)
2222 else:
2223 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002224
2225
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002226def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002227 """Parses the APK certs info from a given target-files zip.
2228
2229 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2230 tuple with the following elements: (1) a dictionary that maps packages to
2231 certs (based on the "certificate" and "private_key" attributes in the file;
2232 (2) a string representing the extension of compressed APKs in the target files
2233 (e.g ".gz", ".bro").
2234
2235 Args:
2236 tf_zip: The input target_files ZipFile (already open).
2237
2238 Returns:
2239 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2240 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2241 no compressed APKs.
2242 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002243 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002244 compressed_extension = None
2245
Tao Bao0f990332017-09-08 19:02:54 -07002246 # META/apkcerts.txt contains the info for _all_ the packages known at build
2247 # time. Filter out the ones that are not installed.
2248 installed_files = set()
2249 for name in tf_zip.namelist():
2250 basename = os.path.basename(name)
2251 if basename:
2252 installed_files.add(basename)
2253
Tao Baoda30cfa2017-12-01 16:19:46 -08002254 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002255 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002256 if not line:
2257 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002258 m = re.match(
2259 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002260 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2261 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002262 line)
2263 if not m:
2264 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002265
Tao Bao818ddf52018-01-05 11:17:34 -08002266 matches = m.groupdict()
2267 cert = matches["CERT"]
2268 privkey = matches["PRIVKEY"]
2269 name = matches["NAME"]
2270 this_compressed_extension = matches["COMPRESSED"]
2271
2272 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2273 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2274 if cert in SPECIAL_CERT_STRINGS and not privkey:
2275 certmap[name] = cert
2276 elif (cert.endswith(OPTIONS.public_key_suffix) and
2277 privkey.endswith(OPTIONS.private_key_suffix) and
2278 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2279 certmap[name] = cert[:-public_key_suffix_len]
2280 else:
2281 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2282
2283 if not this_compressed_extension:
2284 continue
2285
2286 # Only count the installed files.
2287 filename = name + '.' + this_compressed_extension
2288 if filename not in installed_files:
2289 continue
2290
2291 # Make sure that all the values in the compression map have the same
2292 # extension. We don't support multiple compression methods in the same
2293 # system image.
2294 if compressed_extension:
2295 if this_compressed_extension != compressed_extension:
2296 raise ValueError(
2297 "Multiple compressed extensions: {} vs {}".format(
2298 compressed_extension, this_compressed_extension))
2299 else:
2300 compressed_extension = this_compressed_extension
2301
2302 return (certmap,
2303 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002304
2305
Doug Zongkereef39442009-04-02 12:14:19 -07002306COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002307Global options
2308
2309 -p (--path) <dir>
2310 Prepend <dir>/bin to the list of places to search for binaries run by this
2311 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002312
Doug Zongker05d3dea2009-06-22 11:32:31 -07002313 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002314 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002315
Tao Bao30df8b42018-04-23 15:32:53 -07002316 -x (--extra) <key=value>
2317 Add a key/value pair to the 'extras' dict, which device-specific extension
2318 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002319
Doug Zongkereef39442009-04-02 12:14:19 -07002320 -v (--verbose)
2321 Show command lines being executed.
2322
2323 -h (--help)
2324 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002325
2326 --logfile <file>
2327 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002328"""
2329
Kelvin Zhang0876c412020-06-23 15:06:58 -04002330
Doug Zongkereef39442009-04-02 12:14:19 -07002331def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002332 print(docstring.rstrip("\n"))
2333 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002334
2335
2336def ParseOptions(argv,
2337 docstring,
2338 extra_opts="", extra_long_opts=(),
2339 extra_option_handler=None):
2340 """Parse the options in argv and return any arguments that aren't
2341 flags. docstring is the calling module's docstring, to be displayed
2342 for errors and -h. extra_opts and extra_long_opts are for flags
2343 defined by the caller, which are processed by passing them to
2344 extra_option_handler."""
2345
2346 try:
2347 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002348 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002349 ["help", "verbose", "path=", "signapk_path=",
2350 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002351 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002352 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2353 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002354 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2355 "aftl_key_path=", "aftl_manufacturer_key_path=",
2356 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002357 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002358 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002359 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002360 sys.exit(2)
2361
Doug Zongkereef39442009-04-02 12:14:19 -07002362 for o, a in opts:
2363 if o in ("-h", "--help"):
2364 Usage(docstring)
2365 sys.exit()
2366 elif o in ("-v", "--verbose"):
2367 OPTIONS.verbose = True
2368 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002369 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002370 elif o in ("--signapk_path",):
2371 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002372 elif o in ("--signapk_shared_library_path",):
2373 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002374 elif o in ("--extra_signapk_args",):
2375 OPTIONS.extra_signapk_args = shlex.split(a)
2376 elif o in ("--java_path",):
2377 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002378 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002379 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002380 elif o in ("--android_jar_path",):
2381 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002382 elif o in ("--public_key_suffix",):
2383 OPTIONS.public_key_suffix = a
2384 elif o in ("--private_key_suffix",):
2385 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002386 elif o in ("--boot_signer_path",):
2387 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002388 elif o in ("--boot_signer_args",):
2389 OPTIONS.boot_signer_args = shlex.split(a)
2390 elif o in ("--verity_signer_path",):
2391 OPTIONS.verity_signer_path = a
2392 elif o in ("--verity_signer_args",):
2393 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002394 elif o in ("--aftl_tool_path",):
2395 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002396 elif o in ("--aftl_server",):
2397 OPTIONS.aftl_server = a
2398 elif o in ("--aftl_key_path",):
2399 OPTIONS.aftl_key_path = a
2400 elif o in ("--aftl_manufacturer_key_path",):
2401 OPTIONS.aftl_manufacturer_key_path = a
2402 elif o in ("--aftl_signer_helper",):
2403 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002404 elif o in ("-s", "--device_specific"):
2405 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002406 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002407 key, value = a.split("=", 1)
2408 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002409 elif o in ("--logfile",):
2410 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002411 else:
2412 if extra_option_handler is None or not extra_option_handler(o, a):
2413 assert False, "unknown option \"%s\"" % (o,)
2414
Doug Zongker85448772014-09-09 14:59:20 -07002415 if OPTIONS.search_path:
2416 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2417 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002418
2419 return args
2420
2421
Tao Bao4c851b12016-09-19 13:54:38 -07002422def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002423 """Make a temp file and add it to the list of things to be deleted
2424 when Cleanup() is called. Return the filename."""
2425 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2426 os.close(fd)
2427 OPTIONS.tempfiles.append(fn)
2428 return fn
2429
2430
Tao Bao1c830bf2017-12-25 10:43:47 -08002431def MakeTempDir(prefix='tmp', suffix=''):
2432 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2433
2434 Returns:
2435 The absolute pathname of the new directory.
2436 """
2437 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2438 OPTIONS.tempfiles.append(dir_name)
2439 return dir_name
2440
2441
Doug Zongkereef39442009-04-02 12:14:19 -07002442def Cleanup():
2443 for i in OPTIONS.tempfiles:
2444 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002445 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002446 else:
2447 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002448 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002449
2450
2451class PasswordManager(object):
2452 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002453 self.editor = os.getenv("EDITOR")
2454 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002455
2456 def GetPasswords(self, items):
2457 """Get passwords corresponding to each string in 'items',
2458 returning a dict. (The dict may have keys in addition to the
2459 values in 'items'.)
2460
2461 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2462 user edit that file to add more needed passwords. If no editor is
2463 available, or $ANDROID_PW_FILE isn't define, prompts the user
2464 interactively in the ordinary way.
2465 """
2466
2467 current = self.ReadFile()
2468
2469 first = True
2470 while True:
2471 missing = []
2472 for i in items:
2473 if i not in current or not current[i]:
2474 missing.append(i)
2475 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002476 if not missing:
2477 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002478
2479 for i in missing:
2480 current[i] = ""
2481
2482 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002483 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002484 if sys.version_info[0] >= 3:
2485 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002486 answer = raw_input("try to edit again? [y]> ").strip()
2487 if answer and answer[0] not in 'yY':
2488 raise RuntimeError("key passwords unavailable")
2489 first = False
2490
2491 current = self.UpdateAndReadFile(current)
2492
Kelvin Zhang0876c412020-06-23 15:06:58 -04002493 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002494 """Prompt the user to enter a value (password) for each key in
2495 'current' whose value is fales. Returns a new dict with all the
2496 values.
2497 """
2498 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002499 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002500 if v:
2501 result[k] = v
2502 else:
2503 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002504 result[k] = getpass.getpass(
2505 "Enter password for %s key> " % k).strip()
2506 if result[k]:
2507 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002508 return result
2509
2510 def UpdateAndReadFile(self, current):
2511 if not self.editor or not self.pwfile:
2512 return self.PromptResult(current)
2513
2514 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002515 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002516 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2517 f.write("# (Additional spaces are harmless.)\n\n")
2518
2519 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002520 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002521 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002522 f.write("[[[ %s ]]] %s\n" % (v, k))
2523 if not v and first_line is None:
2524 # position cursor on first line with no password.
2525 first_line = i + 4
2526 f.close()
2527
Tao Bao986ee862018-10-04 15:46:16 -07002528 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002529
2530 return self.ReadFile()
2531
2532 def ReadFile(self):
2533 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002534 if self.pwfile is None:
2535 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002536 try:
2537 f = open(self.pwfile, "r")
2538 for line in f:
2539 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002540 if not line or line[0] == '#':
2541 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002542 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2543 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002544 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002545 else:
2546 result[m.group(2)] = m.group(1)
2547 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002548 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002549 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002550 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002551 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002552
2553
Dan Albert8e0178d2015-01-27 15:53:15 -08002554def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2555 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002556
2557 # http://b/18015246
2558 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2559 # for files larger than 2GiB. We can work around this by adjusting their
2560 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2561 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2562 # it isn't clear to me exactly what circumstances cause this).
2563 # `zipfile.write()` must be used directly to work around this.
2564 #
2565 # This mess can be avoided if we port to python3.
2566 saved_zip64_limit = zipfile.ZIP64_LIMIT
2567 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2568
2569 if compress_type is None:
2570 compress_type = zip_file.compression
2571 if arcname is None:
2572 arcname = filename
2573
2574 saved_stat = os.stat(filename)
2575
2576 try:
2577 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2578 # file to be zipped and reset it when we're done.
2579 os.chmod(filename, perms)
2580
2581 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002582 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2583 # intentional. zip stores datetimes in local time without a time zone
2584 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2585 # in the zip archive.
2586 local_epoch = datetime.datetime.fromtimestamp(0)
2587 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002588 os.utime(filename, (timestamp, timestamp))
2589
2590 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2591 finally:
2592 os.chmod(filename, saved_stat.st_mode)
2593 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2594 zipfile.ZIP64_LIMIT = saved_zip64_limit
2595
2596
Tao Bao58c1b962015-05-20 09:32:18 -07002597def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002598 compress_type=None):
2599 """Wrap zipfile.writestr() function to work around the zip64 limit.
2600
2601 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2602 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2603 when calling crc32(bytes).
2604
2605 But it still works fine to write a shorter string into a large zip file.
2606 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2607 when we know the string won't be too long.
2608 """
2609
2610 saved_zip64_limit = zipfile.ZIP64_LIMIT
2611 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2612
2613 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2614 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002615 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002616 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002617 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002618 else:
Tao Baof3282b42015-04-01 11:21:55 -07002619 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002620 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2621 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2622 # such a case (since
2623 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2624 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2625 # permission bits. We follow the logic in Python 3 to get consistent
2626 # behavior between using the two versions.
2627 if not zinfo.external_attr:
2628 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002629
2630 # If compress_type is given, it overrides the value in zinfo.
2631 if compress_type is not None:
2632 zinfo.compress_type = compress_type
2633
Tao Bao58c1b962015-05-20 09:32:18 -07002634 # If perms is given, it has a priority.
2635 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002636 # If perms doesn't set the file type, mark it as a regular file.
2637 if perms & 0o770000 == 0:
2638 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002639 zinfo.external_attr = perms << 16
2640
Tao Baof3282b42015-04-01 11:21:55 -07002641 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002642 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2643
Dan Albert8b72aef2015-03-23 19:13:21 -07002644 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002645 zipfile.ZIP64_LIMIT = saved_zip64_limit
2646
2647
Tao Bao89d7ab22017-12-14 17:05:33 -08002648def ZipDelete(zip_filename, entries):
2649 """Deletes entries from a ZIP file.
2650
2651 Since deleting entries from a ZIP file is not supported, it shells out to
2652 'zip -d'.
2653
2654 Args:
2655 zip_filename: The name of the ZIP file.
2656 entries: The name of the entry, or the list of names to be deleted.
2657
2658 Raises:
2659 AssertionError: In case of non-zero return from 'zip'.
2660 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002661 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002662 entries = [entries]
2663 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002664 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002665
2666
Tao Baof3282b42015-04-01 11:21:55 -07002667def ZipClose(zip_file):
2668 # http://b/18015246
2669 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2670 # central directory.
2671 saved_zip64_limit = zipfile.ZIP64_LIMIT
2672 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2673
2674 zip_file.close()
2675
2676 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002677
2678
2679class DeviceSpecificParams(object):
2680 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002681
Doug Zongker05d3dea2009-06-22 11:32:31 -07002682 def __init__(self, **kwargs):
2683 """Keyword arguments to the constructor become attributes of this
2684 object, which is passed to all functions in the device-specific
2685 module."""
Tao Bao38884282019-07-10 22:20:56 -07002686 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002687 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002688 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002689
2690 if self.module is None:
2691 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002692 if not path:
2693 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002694 try:
2695 if os.path.isdir(path):
2696 info = imp.find_module("releasetools", [path])
2697 else:
2698 d, f = os.path.split(path)
2699 b, x = os.path.splitext(f)
2700 if x == ".py":
2701 f = b
2702 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002703 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002704 self.module = imp.load_module("device_specific", *info)
2705 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002706 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002707
2708 def _DoCall(self, function_name, *args, **kwargs):
2709 """Call the named function in the device-specific module, passing
2710 the given args and kwargs. The first argument to the call will be
2711 the DeviceSpecific object itself. If there is no module, or the
2712 module does not define the function, return the value of the
2713 'default' kwarg (which itself defaults to None)."""
2714 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002715 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002716 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2717
2718 def FullOTA_Assertions(self):
2719 """Called after emitting the block of assertions at the top of a
2720 full OTA package. Implementations can add whatever additional
2721 assertions they like."""
2722 return self._DoCall("FullOTA_Assertions")
2723
Doug Zongkere5ff5902012-01-17 10:55:37 -08002724 def FullOTA_InstallBegin(self):
2725 """Called at the start of full OTA installation."""
2726 return self._DoCall("FullOTA_InstallBegin")
2727
Yifan Hong10c530d2018-12-27 17:34:18 -08002728 def FullOTA_GetBlockDifferences(self):
2729 """Called during full OTA installation and verification.
2730 Implementation should return a list of BlockDifference objects describing
2731 the update on each additional partitions.
2732 """
2733 return self._DoCall("FullOTA_GetBlockDifferences")
2734
Doug Zongker05d3dea2009-06-22 11:32:31 -07002735 def FullOTA_InstallEnd(self):
2736 """Called at the end of full OTA installation; typically this is
2737 used to install the image for the device's baseband processor."""
2738 return self._DoCall("FullOTA_InstallEnd")
2739
2740 def IncrementalOTA_Assertions(self):
2741 """Called after emitting the block of assertions at the top of an
2742 incremental OTA package. Implementations can add whatever
2743 additional assertions they like."""
2744 return self._DoCall("IncrementalOTA_Assertions")
2745
Doug Zongkere5ff5902012-01-17 10:55:37 -08002746 def IncrementalOTA_VerifyBegin(self):
2747 """Called at the start of the verification phase of incremental
2748 OTA installation; additional checks can be placed here to abort
2749 the script before any changes are made."""
2750 return self._DoCall("IncrementalOTA_VerifyBegin")
2751
Doug Zongker05d3dea2009-06-22 11:32:31 -07002752 def IncrementalOTA_VerifyEnd(self):
2753 """Called at the end of the verification phase of incremental OTA
2754 installation; additional checks can be placed here to abort the
2755 script before any changes are made."""
2756 return self._DoCall("IncrementalOTA_VerifyEnd")
2757
Doug Zongkere5ff5902012-01-17 10:55:37 -08002758 def IncrementalOTA_InstallBegin(self):
2759 """Called at the start of incremental OTA installation (after
2760 verification is complete)."""
2761 return self._DoCall("IncrementalOTA_InstallBegin")
2762
Yifan Hong10c530d2018-12-27 17:34:18 -08002763 def IncrementalOTA_GetBlockDifferences(self):
2764 """Called during incremental OTA installation and verification.
2765 Implementation should return a list of BlockDifference objects describing
2766 the update on each additional partitions.
2767 """
2768 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2769
Doug Zongker05d3dea2009-06-22 11:32:31 -07002770 def IncrementalOTA_InstallEnd(self):
2771 """Called at the end of incremental OTA installation; typically
2772 this is used to install the image for the device's baseband
2773 processor."""
2774 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002775
Tao Bao9bc6bb22015-11-09 16:58:28 -08002776 def VerifyOTA_Assertions(self):
2777 return self._DoCall("VerifyOTA_Assertions")
2778
Tao Bao76def242017-11-21 09:25:31 -08002779
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002780class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002781 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002782 self.name = name
2783 self.data = data
2784 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002785 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002786 self.sha1 = sha1(data).hexdigest()
2787
2788 @classmethod
2789 def FromLocalFile(cls, name, diskname):
2790 f = open(diskname, "rb")
2791 data = f.read()
2792 f.close()
2793 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002794
2795 def WriteToTemp(self):
2796 t = tempfile.NamedTemporaryFile()
2797 t.write(self.data)
2798 t.flush()
2799 return t
2800
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002801 def WriteToDir(self, d):
2802 with open(os.path.join(d, self.name), "wb") as fp:
2803 fp.write(self.data)
2804
Geremy Condra36bd3652014-02-06 19:45:10 -08002805 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002806 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002807
Tao Bao76def242017-11-21 09:25:31 -08002808
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002809DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002810 ".gz": "imgdiff",
2811 ".zip": ["imgdiff", "-z"],
2812 ".jar": ["imgdiff", "-z"],
2813 ".apk": ["imgdiff", "-z"],
2814 ".img": "imgdiff",
2815}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002816
Tao Bao76def242017-11-21 09:25:31 -08002817
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002818class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002819 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002820 self.tf = tf
2821 self.sf = sf
2822 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002823 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002824
2825 def ComputePatch(self):
2826 """Compute the patch (as a string of data) needed to turn sf into
2827 tf. Returns the same tuple as GetPatch()."""
2828
2829 tf = self.tf
2830 sf = self.sf
2831
Doug Zongker24cd2802012-08-14 16:36:15 -07002832 if self.diff_program:
2833 diff_program = self.diff_program
2834 else:
2835 ext = os.path.splitext(tf.name)[1]
2836 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002837
2838 ttemp = tf.WriteToTemp()
2839 stemp = sf.WriteToTemp()
2840
2841 ext = os.path.splitext(tf.name)[1]
2842
2843 try:
2844 ptemp = tempfile.NamedTemporaryFile()
2845 if isinstance(diff_program, list):
2846 cmd = copy.copy(diff_program)
2847 else:
2848 cmd = [diff_program]
2849 cmd.append(stemp.name)
2850 cmd.append(ttemp.name)
2851 cmd.append(ptemp.name)
2852 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002853 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002854
Doug Zongkerf8340082014-08-05 10:39:37 -07002855 def run():
2856 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002857 if e:
2858 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002859 th = threading.Thread(target=run)
2860 th.start()
2861 th.join(timeout=300) # 5 mins
2862 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002863 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002864 p.terminate()
2865 th.join(5)
2866 if th.is_alive():
2867 p.kill()
2868 th.join()
2869
Tianjie Xua2a9f992018-01-05 15:15:54 -08002870 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002871 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002872 self.patch = None
2873 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002874 diff = ptemp.read()
2875 finally:
2876 ptemp.close()
2877 stemp.close()
2878 ttemp.close()
2879
2880 self.patch = diff
2881 return self.tf, self.sf, self.patch
2882
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002883 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002884 """Returns a tuple of (target_file, source_file, patch_data).
2885
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002886 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002887 computing the patch failed.
2888 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002889 return self.tf, self.sf, self.patch
2890
2891
2892def ComputeDifferences(diffs):
2893 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002894 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002895
2896 # Do the largest files first, to try and reduce the long-pole effect.
2897 by_size = [(i.tf.size, i) for i in diffs]
2898 by_size.sort(reverse=True)
2899 by_size = [i[1] for i in by_size]
2900
2901 lock = threading.Lock()
2902 diff_iter = iter(by_size) # accessed under lock
2903
2904 def worker():
2905 try:
2906 lock.acquire()
2907 for d in diff_iter:
2908 lock.release()
2909 start = time.time()
2910 d.ComputePatch()
2911 dur = time.time() - start
2912 lock.acquire()
2913
2914 tf, sf, patch = d.GetPatch()
2915 if sf.name == tf.name:
2916 name = tf.name
2917 else:
2918 name = "%s (%s)" % (tf.name, sf.name)
2919 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002920 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002921 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002922 logger.info(
2923 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2924 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002925 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002926 except Exception:
2927 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002928 raise
2929
2930 # start worker threads; wait for them all to finish.
2931 threads = [threading.Thread(target=worker)
2932 for i in range(OPTIONS.worker_threads)]
2933 for th in threads:
2934 th.start()
2935 while threads:
2936 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002937
2938
Dan Albert8b72aef2015-03-23 19:13:21 -07002939class BlockDifference(object):
2940 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002941 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002942 self.tgt = tgt
2943 self.src = src
2944 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002945 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002946 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002947
Tao Baodd2a5892015-03-12 12:32:37 -07002948 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002949 version = max(
2950 int(i) for i in
2951 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002952 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002953 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002954
Tianjie Xu41976c72019-07-03 13:57:01 -07002955 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2956 version=self.version,
2957 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002958 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002959 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002960 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002961 self.touched_src_ranges = b.touched_src_ranges
2962 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002963
Yifan Hong10c530d2018-12-27 17:34:18 -08002964 # On devices with dynamic partitions, for new partitions,
2965 # src is None but OPTIONS.source_info_dict is not.
2966 if OPTIONS.source_info_dict is None:
2967 is_dynamic_build = OPTIONS.info_dict.get(
2968 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002969 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002970 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002971 is_dynamic_build = OPTIONS.source_info_dict.get(
2972 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002973 is_dynamic_source = partition in shlex.split(
2974 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002975
Yifan Hongbb2658d2019-01-25 12:30:58 -08002976 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002977 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2978
Yifan Hongbb2658d2019-01-25 12:30:58 -08002979 # For dynamic partitions builds, check partition list in both source
2980 # and target build because new partitions may be added, and existing
2981 # partitions may be removed.
2982 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2983
Yifan Hong10c530d2018-12-27 17:34:18 -08002984 if is_dynamic:
2985 self.device = 'map_partition("%s")' % partition
2986 else:
2987 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002988 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2989 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002990 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002991 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2992 OPTIONS.source_info_dict)
2993 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002994
Tao Baod8d14be2016-02-04 14:26:02 -08002995 @property
2996 def required_cache(self):
2997 return self._required_cache
2998
Tao Bao76def242017-11-21 09:25:31 -08002999 def WriteScript(self, script, output_zip, progress=None,
3000 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003001 if not self.src:
3002 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003003 script.Print("Patching %s image unconditionally..." % (self.partition,))
3004 else:
3005 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003006
Dan Albert8b72aef2015-03-23 19:13:21 -07003007 if progress:
3008 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003009 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003010
3011 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003012 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003013
Tao Bao9bc6bb22015-11-09 16:58:28 -08003014 def WriteStrictVerifyScript(self, script):
3015 """Verify all the blocks in the care_map, including clobbered blocks.
3016
3017 This differs from the WriteVerifyScript() function: a) it prints different
3018 error messages; b) it doesn't allow half-way updated images to pass the
3019 verification."""
3020
3021 partition = self.partition
3022 script.Print("Verifying %s..." % (partition,))
3023 ranges = self.tgt.care_map
3024 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003025 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003026 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3027 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003028 self.device, ranges_str,
3029 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003030 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003031 script.AppendExtra("")
3032
Tao Baod522bdc2016-04-12 15:53:16 -07003033 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003034 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003035
3036 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003037 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003038 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003039
3040 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003041 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003042 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003043 ranges = self.touched_src_ranges
3044 expected_sha1 = self.touched_src_sha1
3045 else:
3046 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3047 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003048
3049 # No blocks to be checked, skipping.
3050 if not ranges:
3051 return
3052
Tao Bao5ece99d2015-05-12 11:42:31 -07003053 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003054 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003055 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003056 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3057 '"%s.patch.dat")) then' % (
3058 self.device, ranges_str, expected_sha1,
3059 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003060 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003061 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003062
Tianjie Xufc3422a2015-12-15 11:53:59 -08003063 if self.version >= 4:
3064
3065 # Bug: 21124327
3066 # When generating incrementals for the system and vendor partitions in
3067 # version 4 or newer, explicitly check the first block (which contains
3068 # the superblock) of the partition to see if it's what we expect. If
3069 # this check fails, give an explicit log message about the partition
3070 # having been remounted R/W (the most likely explanation).
3071 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003072 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003073
3074 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003075 if partition == "system":
3076 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3077 else:
3078 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003079 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003080 'ifelse (block_image_recover({device}, "{ranges}") && '
3081 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003082 'package_extract_file("{partition}.transfer.list"), '
3083 '"{partition}.new.dat", "{partition}.patch.dat"), '
3084 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003085 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003086 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003087 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003088
Tao Baodd2a5892015-03-12 12:32:37 -07003089 # Abort the OTA update. Note that the incremental OTA cannot be applied
3090 # even if it may match the checksum of the target partition.
3091 # a) If version < 3, operations like move and erase will make changes
3092 # unconditionally and damage the partition.
3093 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003094 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003095 if partition == "system":
3096 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3097 else:
3098 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3099 script.AppendExtra((
3100 'abort("E%d: %s partition has unexpected contents");\n'
3101 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003102
Yifan Hong10c530d2018-12-27 17:34:18 -08003103 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003104 partition = self.partition
3105 script.Print('Verifying the updated %s image...' % (partition,))
3106 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3107 ranges = self.tgt.care_map
3108 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003109 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003110 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003111 self.device, ranges_str,
3112 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003113
3114 # Bug: 20881595
3115 # Verify that extended blocks are really zeroed out.
3116 if self.tgt.extended:
3117 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003118 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003119 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003120 self.device, ranges_str,
3121 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003122 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003123 if partition == "system":
3124 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3125 else:
3126 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003127 script.AppendExtra(
3128 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003129 ' abort("E%d: %s partition has unexpected non-zero contents after '
3130 'OTA update");\n'
3131 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003132 else:
3133 script.Print('Verified the updated %s image.' % (partition,))
3134
Tianjie Xu209db462016-05-24 17:34:52 -07003135 if partition == "system":
3136 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3137 else:
3138 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3139
Tao Bao5fcaaef2015-06-01 13:40:49 -07003140 script.AppendExtra(
3141 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003142 ' abort("E%d: %s partition has unexpected contents after OTA '
3143 'update");\n'
3144 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003145
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003146 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003147 ZipWrite(output_zip,
3148 '{}.transfer.list'.format(self.path),
3149 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003150
Tao Bao76def242017-11-21 09:25:31 -08003151 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3152 # its size. Quailty 9 almost triples the compression time but doesn't
3153 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003154 # zip | brotli(quality 6) | brotli(quality 9)
3155 # compressed_size: 942M | 869M (~8% reduced) | 854M
3156 # compression_time: 75s | 265s | 719s
3157 # decompression_time: 15s | 25s | 25s
3158
3159 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003160 brotli_cmd = ['brotli', '--quality=6',
3161 '--output={}.new.dat.br'.format(self.path),
3162 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003163 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003164 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003165
3166 new_data_name = '{}.new.dat.br'.format(self.partition)
3167 ZipWrite(output_zip,
3168 '{}.new.dat.br'.format(self.path),
3169 new_data_name,
3170 compress_type=zipfile.ZIP_STORED)
3171 else:
3172 new_data_name = '{}.new.dat'.format(self.partition)
3173 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3174
Dan Albert8e0178d2015-01-27 15:53:15 -08003175 ZipWrite(output_zip,
3176 '{}.patch.dat'.format(self.path),
3177 '{}.patch.dat'.format(self.partition),
3178 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003179
Tianjie Xu209db462016-05-24 17:34:52 -07003180 if self.partition == "system":
3181 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3182 else:
3183 code = ErrorCode.VENDOR_UPDATE_FAILURE
3184
Yifan Hong10c530d2018-12-27 17:34:18 -08003185 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003186 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003187 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003188 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003189 device=self.device, partition=self.partition,
3190 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003191 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003192
Kelvin Zhang0876c412020-06-23 15:06:58 -04003193 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003194 data = source.ReadRangeSet(ranges)
3195 ctx = sha1()
3196
3197 for p in data:
3198 ctx.update(p)
3199
3200 return ctx.hexdigest()
3201
Kelvin Zhang0876c412020-06-23 15:06:58 -04003202 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003203 """Return the hash value for all zero blocks."""
3204 zero_block = '\x00' * 4096
3205 ctx = sha1()
3206 for _ in range(num_blocks):
3207 ctx.update(zero_block)
3208
3209 return ctx.hexdigest()
3210
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003211
Tianjie Xu41976c72019-07-03 13:57:01 -07003212# Expose these two classes to support vendor-specific scripts
3213DataImage = images.DataImage
3214EmptyImage = images.EmptyImage
3215
Tao Bao76def242017-11-21 09:25:31 -08003216
Doug Zongker96a57e72010-09-26 14:57:41 -07003217# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003218PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003219 "ext4": "EMMC",
3220 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003221 "f2fs": "EMMC",
3222 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003223}
Doug Zongker96a57e72010-09-26 14:57:41 -07003224
Kelvin Zhang0876c412020-06-23 15:06:58 -04003225
Yifan Hongbdb32012020-05-07 12:38:53 -07003226def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3227 """
3228 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3229 backwards compatibility. It aborts if the fstab entry has slotselect option
3230 (unless check_no_slot is explicitly set to False).
3231 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003232 fstab = info["fstab"]
3233 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003234 if check_no_slot:
3235 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003236 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003237 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3238 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003239 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003240
3241
Yifan Hongbdb32012020-05-07 12:38:53 -07003242def GetTypeAndDeviceExpr(mount_point, info):
3243 """
3244 Return the filesystem of the partition, and an edify expression that evaluates
3245 to the device at runtime.
3246 """
3247 fstab = info["fstab"]
3248 if fstab:
3249 p = fstab[mount_point]
3250 device_expr = '"%s"' % fstab[mount_point].device
3251 if p.slotselect:
3252 device_expr = 'add_slot_suffix(%s)' % device_expr
3253 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003254 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003255
3256
3257def GetEntryForDevice(fstab, device):
3258 """
3259 Returns:
3260 The first entry in fstab whose device is the given value.
3261 """
3262 if not fstab:
3263 return None
3264 for mount_point in fstab:
3265 if fstab[mount_point].device == device:
3266 return fstab[mount_point]
3267 return None
3268
Kelvin Zhang0876c412020-06-23 15:06:58 -04003269
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003270def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003271 """Parses and converts a PEM-encoded certificate into DER-encoded.
3272
3273 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3274
3275 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003276 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003277 """
3278 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003279 save = False
3280 for line in data.split("\n"):
3281 if "--END CERTIFICATE--" in line:
3282 break
3283 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003284 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003285 if "--BEGIN CERTIFICATE--" in line:
3286 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003287 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003288 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003289
Tao Bao04e1f012018-02-04 12:13:35 -08003290
3291def ExtractPublicKey(cert):
3292 """Extracts the public key (PEM-encoded) from the given certificate file.
3293
3294 Args:
3295 cert: The certificate filename.
3296
3297 Returns:
3298 The public key string.
3299
3300 Raises:
3301 AssertionError: On non-zero return from 'openssl'.
3302 """
3303 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3304 # While openssl 1.1 writes the key into the given filename followed by '-out',
3305 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3306 # stdout instead.
3307 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3308 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3309 pubkey, stderrdata = proc.communicate()
3310 assert proc.returncode == 0, \
3311 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3312 return pubkey
3313
3314
Tao Bao1ac886e2019-06-26 11:58:22 -07003315def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003316 """Extracts the AVB public key from the given public or private key.
3317
3318 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003319 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003320 key: The input key file, which should be PEM-encoded public or private key.
3321
3322 Returns:
3323 The path to the extracted AVB public key file.
3324 """
3325 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3326 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003327 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003328 return output
3329
3330
Doug Zongker412c02f2014-02-13 10:58:24 -08003331def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3332 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003333 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003334
Tao Bao6d5d6232018-03-09 17:04:42 -08003335 Most of the space in the boot and recovery images is just the kernel, which is
3336 identical for the two, so the resulting patch should be efficient. Add it to
3337 the output zip, along with a shell script that is run from init.rc on first
3338 boot to actually do the patching and install the new recovery image.
3339
3340 Args:
3341 input_dir: The top-level input directory of the target-files.zip.
3342 output_sink: The callback function that writes the result.
3343 recovery_img: File object for the recovery image.
3344 boot_img: File objects for the boot image.
3345 info_dict: A dict returned by common.LoadInfoDict() on the input
3346 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003347 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003348 if info_dict is None:
3349 info_dict = OPTIONS.info_dict
3350
Tao Bao6d5d6232018-03-09 17:04:42 -08003351 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003352 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3353
3354 if board_uses_vendorimage:
3355 # In this case, the output sink is rooted at VENDOR
3356 recovery_img_path = "etc/recovery.img"
3357 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3358 sh_dir = "bin"
3359 else:
3360 # In this case the output sink is rooted at SYSTEM
3361 recovery_img_path = "vendor/etc/recovery.img"
3362 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3363 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003364
Tao Baof2cffbd2015-07-22 12:33:18 -07003365 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003366 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003367
3368 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003369 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003370 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003371 # With system-root-image, boot and recovery images will have mismatching
3372 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3373 # to handle such a case.
3374 if system_root_image:
3375 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003376 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003377 assert not os.path.exists(path)
3378 else:
3379 diff_program = ["imgdiff"]
3380 if os.path.exists(path):
3381 diff_program.append("-b")
3382 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003383 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003384 else:
3385 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003386
3387 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3388 _, _, patch = d.ComputePatch()
3389 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003390
Dan Albertebb19aa2015-03-27 19:11:53 -07003391 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003392 # The following GetTypeAndDevice()s need to use the path in the target
3393 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003394 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3395 check_no_slot=False)
3396 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3397 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003398 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003399 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003400
Tao Baof2cffbd2015-07-22 12:33:18 -07003401 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003402
3403 # Note that we use /vendor to refer to the recovery resources. This will
3404 # work for a separate vendor partition mounted at /vendor or a
3405 # /system/vendor subdirectory on the system partition, for which init will
3406 # create a symlink from /vendor to /system/vendor.
3407
3408 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003409if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3410 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003411 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003412 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3413 log -t recovery "Installing new recovery image: succeeded" || \\
3414 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003415else
3416 log -t recovery "Recovery image already installed"
3417fi
3418""" % {'type': recovery_type,
3419 'device': recovery_device,
3420 'sha1': recovery_img.sha1,
3421 'size': recovery_img.size}
3422 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003423 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003424if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3425 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003426 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003427 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3428 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3429 log -t recovery "Installing new recovery image: succeeded" || \\
3430 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003431else
3432 log -t recovery "Recovery image already installed"
3433fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003434""" % {'boot_size': boot_img.size,
3435 'boot_sha1': boot_img.sha1,
3436 'recovery_size': recovery_img.size,
3437 'recovery_sha1': recovery_img.sha1,
3438 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003439 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003440 'recovery_type': recovery_type,
3441 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003442 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003443
Bill Peckhame868aec2019-09-17 17:06:47 -07003444 # The install script location moved from /system/etc to /system/bin in the L
3445 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3446 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003447
Tao Bao32fcdab2018-10-12 10:30:39 -07003448 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003449
Tao Baoda30cfa2017-12-01 16:19:46 -08003450 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003451
3452
3453class DynamicPartitionUpdate(object):
3454 def __init__(self, src_group=None, tgt_group=None, progress=None,
3455 block_difference=None):
3456 self.src_group = src_group
3457 self.tgt_group = tgt_group
3458 self.progress = progress
3459 self.block_difference = block_difference
3460
3461 @property
3462 def src_size(self):
3463 if not self.block_difference:
3464 return 0
3465 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3466
3467 @property
3468 def tgt_size(self):
3469 if not self.block_difference:
3470 return 0
3471 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3472
3473 @staticmethod
3474 def _GetSparseImageSize(img):
3475 if not img:
3476 return 0
3477 return img.blocksize * img.total_blocks
3478
3479
3480class DynamicGroupUpdate(object):
3481 def __init__(self, src_size=None, tgt_size=None):
3482 # None: group does not exist. 0: no size limits.
3483 self.src_size = src_size
3484 self.tgt_size = tgt_size
3485
3486
3487class DynamicPartitionsDifference(object):
3488 def __init__(self, info_dict, block_diffs, progress_dict=None,
3489 source_info_dict=None):
3490 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003491 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003492
3493 self._remove_all_before_apply = False
3494 if source_info_dict is None:
3495 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003496 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003497
Tao Baof1113e92019-06-18 12:10:14 -07003498 block_diff_dict = collections.OrderedDict(
3499 [(e.partition, e) for e in block_diffs])
3500
Yifan Hong10c530d2018-12-27 17:34:18 -08003501 assert len(block_diff_dict) == len(block_diffs), \
3502 "Duplicated BlockDifference object for {}".format(
3503 [partition for partition, count in
3504 collections.Counter(e.partition for e in block_diffs).items()
3505 if count > 1])
3506
Yifan Hong79997e52019-01-23 16:56:19 -08003507 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003508
3509 for p, block_diff in block_diff_dict.items():
3510 self._partition_updates[p] = DynamicPartitionUpdate()
3511 self._partition_updates[p].block_difference = block_diff
3512
3513 for p, progress in progress_dict.items():
3514 if p in self._partition_updates:
3515 self._partition_updates[p].progress = progress
3516
3517 tgt_groups = shlex.split(info_dict.get(
3518 "super_partition_groups", "").strip())
3519 src_groups = shlex.split(source_info_dict.get(
3520 "super_partition_groups", "").strip())
3521
3522 for g in tgt_groups:
3523 for p in shlex.split(info_dict.get(
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003524 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003525 assert p in self._partition_updates, \
3526 "{} is in target super_{}_partition_list but no BlockDifference " \
3527 "object is provided.".format(p, g)
3528 self._partition_updates[p].tgt_group = g
3529
3530 for g in src_groups:
3531 for p in shlex.split(source_info_dict.get(
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003532 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003533 assert p in self._partition_updates, \
3534 "{} is in source super_{}_partition_list but no BlockDifference " \
3535 "object is provided.".format(p, g)
3536 self._partition_updates[p].src_group = g
3537
Yifan Hong45433e42019-01-18 13:55:25 -08003538 target_dynamic_partitions = set(shlex.split(info_dict.get(
3539 "dynamic_partition_list", "").strip()))
3540 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3541 if u.tgt_size)
3542 assert block_diffs_with_target == target_dynamic_partitions, \
3543 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3544 list(target_dynamic_partitions), list(block_diffs_with_target))
3545
3546 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3547 "dynamic_partition_list", "").strip()))
3548 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3549 if u.src_size)
3550 assert block_diffs_with_source == source_dynamic_partitions, \
3551 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3552 list(source_dynamic_partitions), list(block_diffs_with_source))
3553
Yifan Hong10c530d2018-12-27 17:34:18 -08003554 if self._partition_updates:
3555 logger.info("Updating dynamic partitions %s",
3556 self._partition_updates.keys())
3557
Yifan Hong79997e52019-01-23 16:56:19 -08003558 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003559
3560 for g in tgt_groups:
3561 self._group_updates[g] = DynamicGroupUpdate()
3562 self._group_updates[g].tgt_size = int(info_dict.get(
3563 "super_%s_group_size" % g, "0").strip())
3564
3565 for g in src_groups:
3566 if g not in self._group_updates:
3567 self._group_updates[g] = DynamicGroupUpdate()
3568 self._group_updates[g].src_size = int(source_info_dict.get(
3569 "super_%s_group_size" % g, "0").strip())
3570
3571 self._Compute()
3572
3573 def WriteScript(self, script, output_zip, write_verify_script=False):
3574 script.Comment('--- Start patching dynamic partitions ---')
3575 for p, u in self._partition_updates.items():
3576 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3577 script.Comment('Patch partition %s' % p)
3578 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3579 write_verify_script=False)
3580
3581 op_list_path = MakeTempFile()
3582 with open(op_list_path, 'w') as f:
3583 for line in self._op_list:
3584 f.write('{}\n'.format(line))
3585
3586 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3587
3588 script.Comment('Update dynamic partition metadata')
3589 script.AppendExtra('assert(update_dynamic_partitions('
3590 'package_extract_file("dynamic_partitions_op_list")));')
3591
3592 if write_verify_script:
3593 for p, u in self._partition_updates.items():
3594 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3595 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003596 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003597
3598 for p, u in self._partition_updates.items():
3599 if u.tgt_size and u.src_size <= u.tgt_size:
3600 script.Comment('Patch partition %s' % p)
3601 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3602 write_verify_script=write_verify_script)
3603 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003604 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003605
3606 script.Comment('--- End patching dynamic partitions ---')
3607
3608 def _Compute(self):
3609 self._op_list = list()
3610
3611 def append(line):
3612 self._op_list.append(line)
3613
3614 def comment(line):
3615 self._op_list.append("# %s" % line)
3616
3617 if self._remove_all_before_apply:
3618 comment('Remove all existing dynamic partitions and groups before '
3619 'applying full OTA')
3620 append('remove_all_groups')
3621
3622 for p, u in self._partition_updates.items():
3623 if u.src_group and not u.tgt_group:
3624 append('remove %s' % p)
3625
3626 for p, u in self._partition_updates.items():
3627 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3628 comment('Move partition %s from %s to default' % (p, u.src_group))
3629 append('move %s default' % p)
3630
3631 for p, u in self._partition_updates.items():
3632 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3633 comment('Shrink partition %s from %d to %d' %
3634 (p, u.src_size, u.tgt_size))
3635 append('resize %s %s' % (p, u.tgt_size))
3636
3637 for g, u in self._group_updates.items():
3638 if u.src_size is not None and u.tgt_size is None:
3639 append('remove_group %s' % g)
3640 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003641 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003642 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3643 append('resize_group %s %d' % (g, u.tgt_size))
3644
3645 for g, u in self._group_updates.items():
3646 if u.src_size is None and u.tgt_size is not None:
3647 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3648 append('add_group %s %d' % (g, u.tgt_size))
3649 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003650 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003651 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3652 append('resize_group %s %d' % (g, u.tgt_size))
3653
3654 for p, u in self._partition_updates.items():
3655 if u.tgt_group and not u.src_group:
3656 comment('Add partition %s to group %s' % (p, u.tgt_group))
3657 append('add %s %s' % (p, u.tgt_group))
3658
3659 for p, u in self._partition_updates.items():
3660 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003661 comment('Grow partition %s from %d to %d' %
3662 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003663 append('resize %s %d' % (p, u.tgt_size))
3664
3665 for p, u in self._partition_updates.items():
3666 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3667 comment('Move partition %s from default to %s' %
3668 (p, u.tgt_group))
3669 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003670
3671
Yifan Hong85ac5012021-01-07 14:43:46 -08003672def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003673 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003674 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003675
3676 Args:
3677 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3678
3679 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003680 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003681 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003682 tmp_dir = MakeTempDir('boot_', suffix='.img')
3683 try:
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003684 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3685 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003686 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3687 if not os.path.isfile(ramdisk):
3688 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3689 return None
3690 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3691 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3692
3693 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3694 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3695 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3696 # the host environment.
3697 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003698 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003699
Yifan Hongc65a0542021-01-07 14:21:01 -08003700 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3701 prop_file = os.path.join(extracted_ramdisk, search_path)
3702 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003703 return prop_file
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003704 logger.warning(
3705 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003706
Yifan Hong7dc51172021-01-12 11:27:39 -08003707 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003708
Yifan Hong85ac5012021-01-07 14:43:46 -08003709 except ExternalError as e:
3710 logger.warning('Unable to get boot image build props: %s', e)
3711 return None
3712
3713
3714def GetBootImageTimestamp(boot_img):
3715 """
3716 Get timestamp from ramdisk within the boot image
3717
3718 Args:
3719 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3720
3721 Return:
3722 An integer that corresponds to the timestamp of the boot image, or None
3723 if file has unknown format. Raise exception if an unexpected error has
3724 occurred.
3725 """
3726 prop_file = GetBootImageBuildProp(boot_img)
3727 if not prop_file:
3728 return None
3729
3730 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3731 if props is None:
3732 return None
3733
3734 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003735 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3736 if timestamp:
3737 return int(timestamp)
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003738 logger.warning(
3739 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003740 return None
3741
3742 except ExternalError as e:
3743 logger.warning('Unable to get boot image timestamp: %s', e)
3744 return None
Kelvin Zhangc184fa12021-03-22 15:38:38 -04003745
3746
3747def GetCareMap(which, imgname):
3748 """Returns the care_map string for the given partition.
3749
3750 Args:
3751 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3752 imgname: The filename of the image.
3753
3754 Returns:
3755 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3756 RangeSet; or None.
3757 """
3758 assert which in PARTITIONS_WITH_CARE_MAP
3759
3760 # which + "_image_size" contains the size that the actual filesystem image
3761 # resides in, which is all that needs to be verified. The additional blocks in
3762 # the image file contain verity metadata, by reading which would trigger
3763 # invalid reads.
3764 image_size = OPTIONS.info_dict.get(which + "_image_size")
3765 if not image_size:
3766 return None
3767
3768 image_blocks = int(image_size) // 4096 - 1
3769 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3770
3771 # For sparse images, we will only check the blocks that are listed in the care
3772 # map, i.e. the ones with meaningful data.
3773 if "extfs_sparse_flag" in OPTIONS.info_dict:
3774 simg = sparse_img.SparseImage(imgname)
3775 care_map_ranges = simg.care_map.intersect(
3776 rangelib.RangeSet("0-{}".format(image_blocks)))
3777
3778 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3779 # image.
3780 else:
3781 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3782
3783 return [which, care_map_ranges.to_string_raw()]
3784
3785
3786def AddCareMapForAbOta(output_zip, ab_partitions, image_paths):
3787 """Generates and adds care_map.pb for a/b partition that has care_map.
3788
3789 Args:
3790 output_zip: The output zip file (needs to be already open), or None to
3791 write care_map.pb to OPTIONS.input_tmp/.
3792 ab_partitions: The list of A/B partitions.
3793 image_paths: A map from the partition name to the image path.
3794 """
3795 care_map_list = []
3796 for partition in ab_partitions:
3797 partition = partition.strip()
3798 if partition not in PARTITIONS_WITH_CARE_MAP:
3799 continue
3800
3801 verity_block_device = "{}_verity_block_device".format(partition)
3802 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3803 if (verity_block_device in OPTIONS.info_dict or
3804 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3805 image_path = image_paths[partition]
3806 assert os.path.exists(image_path)
3807
3808 care_map = GetCareMap(partition, image_path)
3809 if not care_map:
3810 continue
3811 care_map_list += care_map
3812
3813 # adds fingerprint field to the care_map
3814 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3815 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3816 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3817 "ro.{}.build.thumbprint".format(partition)]
3818
3819 present_props = [x for x in prop_name_list if
3820 partition_props and partition_props.GetProp(x)]
3821 if not present_props:
3822 logger.warning(
3823 "fingerprint is not present for partition %s", partition)
3824 property_id, fingerprint = "unknown", "unknown"
3825 else:
3826 property_id = present_props[0]
3827 fingerprint = partition_props.GetProp(property_id)
3828 care_map_list += [property_id, fingerprint]
3829
3830 if not care_map_list:
3831 return
3832
3833 # Converts the list into proto buf message by calling care_map_generator; and
3834 # writes the result to a temp file.
3835 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3836 suffix=".txt")
3837 with open(temp_care_map_text, 'w') as text_file:
3838 text_file.write('\n'.join(care_map_list))
3839
3840 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3841 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3842 RunAndCheckOutput(care_map_gen_cmd)
3843
3844 care_map_path = "META/care_map.pb"
3845 if output_zip and care_map_path not in output_zip.namelist():
3846 ZipWrite(output_zip, temp_care_map, arcname=care_map_path)
3847 else:
3848 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3849 if output_zip:
3850 OPTIONS.replace_updated_files_list.append(care_map_path)