blob: b397fd041603f6c8b4463004a0531322d6202427 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Kelvin Zhang27324132021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
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 Zhang563750f2021-04-28 12:46:17 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhang563750f2021-04-28 12:46:17 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhang563750f2021-04-28 12:46:17 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
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
Kelvin Zhang563750f2021-04-28 12:46:17 -0400440 def is_vabc(self):
441 vendor_prop = self.info_dict.get("vendor.build.prop")
442 vabc_enabled = vendor_prop and \
443 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
444 return vabc_enabled
445
446 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700447 def oem_props(self):
448 return self._oem_props
449
450 def __getitem__(self, key):
451 return self.info_dict[key]
452
453 def __setitem__(self, key, value):
454 self.info_dict[key] = value
455
456 def get(self, key, default=None):
457 return self.info_dict.get(key, default)
458
459 def items(self):
460 return self.info_dict.items()
461
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000462 def _GetRawBuildProp(self, prop, partition):
463 prop_file = '{}.build.prop'.format(
464 partition) if partition else 'build.prop'
465 partition_props = self.info_dict.get(prop_file)
466 if not partition_props:
467 return None
468 return partition_props.GetProp(prop)
469
Daniel Normand5fe8622020-01-08 17:01:11 -0800470 def GetPartitionBuildProp(self, prop, partition):
471 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800472
473 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400474 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800475
Daniel Normand5fe8622020-01-08 17:01:11 -0800476 # If provided a partition for this property, only look within that
477 # partition's build.prop.
478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800479 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800480 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800481 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000482
483 prop_val = self._GetRawBuildProp(prop, partition)
484 if prop_val is not None:
485 return prop_val
486 raise ExternalError("couldn't find %s in %s.build.prop" %
487 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800488
Tao Bao1c320f82019-10-04 23:25:12 -0700489 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800490 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700491 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
492 return self._ResolveRoProductBuildProp(prop)
493
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 prop_val = self._GetRawBuildProp(prop, None)
495 if prop_val is not None:
496 return prop_val
497
498 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700499
500 def _ResolveRoProductBuildProp(self, prop):
501 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700503 if prop_val:
504 return prop_val
505
Steven Laver8e2086e2020-04-27 16:26:31 -0700506 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000507 source_order_val = self._GetRawBuildProp(
508 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700509 if source_order_val:
510 source_order = source_order_val.split(",")
511 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700512 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700513
514 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700516 raise ExternalError(
517 "Invalid ro.product.property_source_order '{}'".format(source_order))
518
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000519 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700520 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000521 "ro.product", "ro.product.{}".format(source_partition), 1)
522 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700523 if prop_val:
524 return prop_val
525
526 raise ExternalError("couldn't resolve {}".format(prop))
527
Steven Laver8e2086e2020-04-27 16:26:31 -0700528 def _GetRoProductPropsDefaultSourceOrder(self):
529 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
530 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000531 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700532 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000533 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700534 if android_version == "10":
535 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
536 # NOTE: float() conversion of android_version will have rounding error.
537 # We are checking for "9" or less, and using "< 10" is well outside of
538 # possible floating point rounding.
539 try:
540 android_version_val = float(android_version)
541 except ValueError:
542 android_version_val = 0
543 if android_version_val < 10:
544 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
545 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
546
Tianjieb37c5be2020-10-15 21:27:10 -0700547 def _GetPlatformVersion(self):
548 version_sdk = self.GetBuildProp("ro.build.version.sdk")
549 # init code switches to version_release_or_codename (see b/158483506). After
550 # API finalization, release_or_codename will be the same as release. This
551 # is the best effort to support pre-S dev stage builds.
552 if int(version_sdk) >= 30:
553 try:
554 return self.GetBuildProp("ro.build.version.release_or_codename")
555 except ExternalError:
556 logger.warning('Failed to find ro.build.version.release_or_codename')
557
558 return self.GetBuildProp("ro.build.version.release")
559
560 def _GetPartitionPlatformVersion(self, partition):
561 try:
562 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
563 partition)
564 except ExternalError:
565 return self.GetPartitionBuildProp("ro.build.version.release",
566 partition)
567
Tao Bao1c320f82019-10-04 23:25:12 -0700568 def GetOemProperty(self, key):
569 if self.oem_props is not None and key in self.oem_props:
570 return self.oem_dicts[0][key]
571 return self.GetBuildProp(key)
572
Daniel Normand5fe8622020-01-08 17:01:11 -0800573 def GetPartitionFingerprint(self, partition):
574 return self._partition_fingerprints.get(partition, None)
575
576 def CalculatePartitionFingerprint(self, partition):
577 try:
578 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
579 except ExternalError:
580 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
581 self.GetPartitionBuildProp("ro.product.brand", partition),
582 self.GetPartitionBuildProp("ro.product.name", partition),
583 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700584 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800585 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400586 self.GetPartitionBuildProp(
587 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800588 self.GetPartitionBuildProp("ro.build.type", partition),
589 self.GetPartitionBuildProp("ro.build.tags", partition))
590
Tao Bao1c320f82019-10-04 23:25:12 -0700591 def CalculateFingerprint(self):
592 if self.oem_props is None:
593 try:
594 return self.GetBuildProp("ro.build.fingerprint")
595 except ExternalError:
596 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
597 self.GetBuildProp("ro.product.brand"),
598 self.GetBuildProp("ro.product.name"),
599 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700600 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700601 self.GetBuildProp("ro.build.id"),
602 self.GetBuildProp("ro.build.version.incremental"),
603 self.GetBuildProp("ro.build.type"),
604 self.GetBuildProp("ro.build.tags"))
605 return "%s/%s/%s:%s" % (
606 self.GetOemProperty("ro.product.brand"),
607 self.GetOemProperty("ro.product.name"),
608 self.GetOemProperty("ro.product.device"),
609 self.GetBuildProp("ro.build.thumbprint"))
610
611 def WriteMountOemScript(self, script):
612 assert self.oem_props is not None
613 recovery_mount_options = self.info_dict.get("recovery_mount_options")
614 script.Mount("/oem", recovery_mount_options)
615
616 def WriteDeviceAssertions(self, script, oem_no_mount):
617 # Read the property directly if not using OEM properties.
618 if not self.oem_props:
619 script.AssertDevice(self.device)
620 return
621
622 # Otherwise assert OEM properties.
623 if not self.oem_dicts:
624 raise ExternalError(
625 "No OEM file provided to answer expected assertions")
626
627 for prop in self.oem_props.split():
628 values = []
629 for oem_dict in self.oem_dicts:
630 if prop in oem_dict:
631 values.append(oem_dict[prop])
632 if not values:
633 raise ExternalError(
634 "The OEM file is missing the property %s" % (prop,))
635 script.AssertOemProperty(prop, values, oem_no_mount)
636
637
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000638def ReadFromInputFile(input_file, fn):
639 """Reads the contents of fn from input zipfile or directory."""
640 if isinstance(input_file, zipfile.ZipFile):
641 return input_file.read(fn).decode()
642 else:
643 path = os.path.join(input_file, *fn.split("/"))
644 try:
645 with open(path) as f:
646 return f.read()
647 except IOError as e:
648 if e.errno == errno.ENOENT:
649 raise KeyError(fn)
650
651
Yifan Hong10482a22021-01-07 14:38:41 -0800652def ExtractFromInputFile(input_file, fn):
653 """Extracts the contents of fn from input zipfile or directory into a file."""
654 if isinstance(input_file, zipfile.ZipFile):
655 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500656 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800657 f.write(input_file.read(fn))
658 return tmp_file
659 else:
660 file = os.path.join(input_file, *fn.split("/"))
661 if not os.path.exists(file):
662 raise KeyError(fn)
663 return file
664
Kelvin Zhang563750f2021-04-28 12:46:17 -0400665
jiajia tangf3f842b2021-03-17 21:49:44 +0800666class RamdiskFormat(object):
667 LZ4 = 1
668 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800669
Kelvin Zhang563750f2021-04-28 12:46:17 -0400670
jiajia tang836f76b2021-04-02 14:48:26 +0800671def _GetRamdiskFormat(info_dict):
672 if info_dict.get('lz4_ramdisks') == 'true':
673 ramdisk_format = RamdiskFormat.LZ4
674 else:
675 ramdisk_format = RamdiskFormat.GZ
676 return ramdisk_format
677
Kelvin Zhang563750f2021-04-28 12:46:17 -0400678
Tao Bao410ad8b2018-08-24 12:08:38 -0700679def LoadInfoDict(input_file, repacking=False):
680 """Loads the key/value pairs from the given input target_files.
681
Tianjiea85bdf02020-07-29 11:56:19 -0700682 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700683 checks and returns the parsed key/value pairs for to the given build. It's
684 usually called early when working on input target_files files, e.g. when
685 generating OTAs, or signing builds. Note that the function may be called
686 against an old target_files file (i.e. from past dessert releases). So the
687 property parsing needs to be backward compatible.
688
689 In a `META/misc_info.txt`, a few properties are stored as links to the files
690 in the PRODUCT_OUT directory. It works fine with the build system. However,
691 they are no longer available when (re)generating images from target_files zip.
692 When `repacking` is True, redirect these properties to the actual files in the
693 unzipped directory.
694
695 Args:
696 input_file: The input target_files file, which could be an open
697 zipfile.ZipFile instance, or a str for the dir that contains the files
698 unzipped from a target_files file.
699 repacking: Whether it's trying repack an target_files file after loading the
700 info dict (default: False). If so, it will rewrite a few loaded
701 properties (e.g. selinux_fc, root_dir) to point to the actual files in
702 target_files file. When doing repacking, `input_file` must be a dir.
703
704 Returns:
705 A dict that contains the parsed key/value pairs.
706
707 Raises:
708 AssertionError: On invalid input arguments.
709 ValueError: On malformed input values.
710 """
711 if repacking:
712 assert isinstance(input_file, str), \
713 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700714
Doug Zongkerc9253822014-02-04 12:17:58 -0800715 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000716 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800717
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700718 try:
Michael Runge6e836112014-04-15 17:40:21 -0700719 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700720 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700721 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700722
Tao Bao410ad8b2018-08-24 12:08:38 -0700723 if "recovery_api_version" not in d:
724 raise ValueError("Failed to find 'recovery_api_version'")
725 if "fstab_version" not in d:
726 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800727
Tao Bao410ad8b2018-08-24 12:08:38 -0700728 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700729 # "selinux_fc" properties should point to the file_contexts files
730 # (file_contexts.bin) under META/.
731 for key in d:
732 if key.endswith("selinux_fc"):
733 fc_basename = os.path.basename(d[key])
734 fc_config = os.path.join(input_file, "META", fc_basename)
735 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700736
Daniel Norman72c626f2019-05-13 15:58:14 -0700737 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700738
Tom Cherryd14b8952018-08-09 14:26:00 -0700739 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700740 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700741 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700742 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700743
David Anderson0ec64ac2019-12-06 12:21:18 -0800744 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700745 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700746 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800747 key_name = part_name + "_base_fs_file"
748 if key_name not in d:
749 continue
750 basename = os.path.basename(d[key_name])
751 base_fs_file = os.path.join(input_file, "META", basename)
752 if os.path.exists(base_fs_file):
753 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700754 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700755 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800756 "Failed to find %s base fs file: %s", part_name, base_fs_file)
757 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700758
Doug Zongker37974732010-09-16 17:44:38 -0700759 def makeint(key):
760 if key in d:
761 d[key] = int(d[key], 0)
762
763 makeint("recovery_api_version")
764 makeint("blocksize")
765 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700766 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700767 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700768 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700769 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800770 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700771
Steve Muckle903a1ca2020-05-07 17:32:10 -0700772 boot_images = "boot.img"
773 if "boot_images" in d:
774 boot_images = d["boot_images"]
775 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400776 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700777
Tao Bao765668f2019-10-04 22:03:00 -0700778 # Load recovery fstab if applicable.
779 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800780 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800781
Tianjie Xu861f4132018-09-12 11:49:33 -0700782 # Tries to load the build props for all partitions with care_map, including
783 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800784 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800785 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000786 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800787 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700788 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800789
Tao Bao3ed35d32019-10-07 20:48:48 -0700790 # Set up the salt (based on fingerprint) that will be used when adding AVB
791 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800792 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700793 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800794 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800795 fingerprint = build_info.GetPartitionFingerprint(partition)
796 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400797 d["avb_{}_salt".format(partition)] = sha256(
798 fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400799 try:
800 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
801 except KeyError:
802 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700803 return d
804
Tao Baod1de6f32017-03-01 16:38:48 -0800805
Daniel Norman4cc9df62019-07-18 10:11:07 -0700806def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900807 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700808 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900809
Daniel Norman4cc9df62019-07-18 10:11:07 -0700810
811def LoadDictionaryFromFile(file_path):
812 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900813 return LoadDictionaryFromLines(lines)
814
815
Michael Runge6e836112014-04-15 17:40:21 -0700816def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700817 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700818 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700819 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700820 if not line or line.startswith("#"):
821 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700822 if "=" in line:
823 name, value = line.split("=", 1)
824 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700825 return d
826
Tao Baod1de6f32017-03-01 16:38:48 -0800827
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000828class PartitionBuildProps(object):
829 """The class holds the build prop of a particular partition.
830
831 This class loads the build.prop and holds the build properties for a given
832 partition. It also partially recognizes the 'import' statement in the
833 build.prop; and calculates alternative values of some specific build
834 properties during runtime.
835
836 Attributes:
837 input_file: a zipped target-file or an unzipped target-file directory.
838 partition: name of the partition.
839 props_allow_override: a list of build properties to search for the
840 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 build_props: a dict of build properties for the given partition.
842 prop_overrides: a set of props that are overridden by import.
843 placeholder_values: A dict of runtime variables' values to replace the
844 placeholders in the build.prop file. We expect exactly one value for
845 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800846 ramdisk_format: If name is "boot", the format of ramdisk inside the
847 boot image. Otherwise, its value is ignored.
848 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000849 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400850
Tianjie Xu9afb2212020-05-10 21:48:15 +0000851 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000852 self.input_file = input_file
853 self.partition = name
854 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000855 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000856 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000857 self.prop_overrides = set()
858 self.placeholder_values = {}
859 if placeholder_values:
860 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000861
862 @staticmethod
863 def FromDictionary(name, build_props):
864 """Constructs an instance from a build prop dictionary."""
865
866 props = PartitionBuildProps("unknown", name)
867 props.build_props = build_props.copy()
868 return props
869
870 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800871 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000872 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800873
874 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400875 data = PartitionBuildProps._ReadBootPropFile(
876 input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800877 else:
878 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
879
880 props = PartitionBuildProps(input_file, name, placeholder_values)
881 props._LoadBuildProp(data)
882 return props
883
884 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800885 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800886 """
887 Read build.prop for boot image from input_file.
888 Return empty string if not found.
889 """
890 try:
891 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
892 except KeyError:
893 logger.warning('Failed to read IMAGES/boot.img')
894 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800895 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800896 if prop_file is None:
897 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500898 with open(prop_file, "r") as f:
899 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800900
901 @staticmethod
902 def _ReadPartitionPropFile(input_file, name):
903 """
904 Read build.prop for name from input_file.
905 Return empty string if not found.
906 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000907 data = ''
908 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
909 '{}/build.prop'.format(name.upper())]:
910 try:
911 data = ReadFromInputFile(input_file, prop_file)
912 break
913 except KeyError:
914 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800915 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000916
Yifan Hong125d0b62020-09-24 17:07:03 -0700917 @staticmethod
918 def FromBuildPropFile(name, build_prop_file):
919 """Constructs an instance from a build prop file."""
920
921 props = PartitionBuildProps("unknown", name)
922 with open(build_prop_file) as f:
923 props._LoadBuildProp(f.read())
924 return props
925
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 def _LoadBuildProp(self, data):
927 for line in data.split('\n'):
928 line = line.strip()
929 if not line or line.startswith("#"):
930 continue
931 if line.startswith("import"):
932 overrides = self._ImportParser(line)
933 duplicates = self.prop_overrides.intersection(overrides.keys())
934 if duplicates:
935 raise ValueError('prop {} is overridden multiple times'.format(
936 ','.join(duplicates)))
937 self.prop_overrides = self.prop_overrides.union(overrides.keys())
938 self.build_props.update(overrides)
939 elif "=" in line:
940 name, value = line.split("=", 1)
941 if name in self.prop_overrides:
942 raise ValueError('prop {} is set again after overridden by import '
943 'statement'.format(name))
944 self.build_props[name] = value
945
946 def _ImportParser(self, line):
947 """Parses the build prop in a given import statement."""
948
949 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400950 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000951 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700952
953 if len(tokens) == 3:
954 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
955 return {}
956
Tianjie Xu9afb2212020-05-10 21:48:15 +0000957 import_path = tokens[1]
958 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
959 raise ValueError('Unrecognized import path {}'.format(line))
960
961 # We only recognize a subset of import statement that the init process
962 # supports. And we can loose the restriction based on how the dynamic
963 # fingerprint is used in practice. The placeholder format should be
964 # ${placeholder}, and its value should be provided by the caller through
965 # the placeholder_values.
966 for prop, value in self.placeholder_values.items():
967 prop_place_holder = '${{{}}}'.format(prop)
968 if prop_place_holder in import_path:
969 import_path = import_path.replace(prop_place_holder, value)
970 if '$' in import_path:
971 logger.info('Unresolved place holder in import path %s', import_path)
972 return {}
973
974 import_path = import_path.replace('/{}'.format(self.partition),
975 self.partition.upper())
976 logger.info('Parsing build props override from %s', import_path)
977
978 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
979 d = LoadDictionaryFromLines(lines)
980 return {key: val for key, val in d.items()
981 if key in self.props_allow_override}
982
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000983 def GetProp(self, prop):
984 return self.build_props.get(prop)
985
986
Tianjie Xucfa86222016-03-07 16:31:19 -0800987def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
988 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700989 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700990 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700991 self.mount_point = mount_point
992 self.fs_type = fs_type
993 self.device = device
994 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700995 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700996 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700997
998 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800999 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001000 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001001 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001002 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001003
Tao Baod1de6f32017-03-01 16:38:48 -08001004 assert fstab_version == 2
1005
1006 d = {}
1007 for line in data.split("\n"):
1008 line = line.strip()
1009 if not line or line.startswith("#"):
1010 continue
1011
1012 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1013 pieces = line.split()
1014 if len(pieces) != 5:
1015 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1016
1017 # Ignore entries that are managed by vold.
1018 options = pieces[4]
1019 if "voldmanaged=" in options:
1020 continue
1021
1022 # It's a good line, parse it.
1023 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001024 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001025 options = options.split(",")
1026 for i in options:
1027 if i.startswith("length="):
1028 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001029 elif i == "slotselect":
1030 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001031 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001032 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001033 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001034
Tao Baod1de6f32017-03-01 16:38:48 -08001035 mount_flags = pieces[3]
1036 # Honor the SELinux context if present.
1037 context = None
1038 for i in mount_flags.split(","):
1039 if i.startswith("context="):
1040 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001041
Tao Baod1de6f32017-03-01 16:38:48 -08001042 mount_point = pieces[1]
1043 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001044 device=pieces[0], length=length, context=context,
1045 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001046
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001047 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001048 # system. Other areas assume system is always at "/system" so point /system
1049 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001050 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001051 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001052 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001053 return d
1054
1055
Tao Bao765668f2019-10-04 22:03:00 -07001056def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1057 """Finds the path to recovery fstab and loads its contents."""
1058 # recovery fstab is only meaningful when installing an update via recovery
1059 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001060 if info_dict.get('ab_update') == 'true' and \
1061 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001062 return None
1063
1064 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1065 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1066 # cases, since it may load the info_dict from an old build (e.g. when
1067 # generating incremental OTAs from that build).
1068 system_root_image = info_dict.get('system_root_image') == 'true'
1069 if info_dict.get('no_recovery') != 'true':
1070 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1071 if isinstance(input_file, zipfile.ZipFile):
1072 if recovery_fstab_path not in input_file.namelist():
1073 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1074 else:
1075 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1076 if not os.path.exists(path):
1077 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1078 return LoadRecoveryFSTab(
1079 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1080 system_root_image)
1081
1082 if info_dict.get('recovery_as_boot') == 'true':
1083 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1084 if isinstance(input_file, zipfile.ZipFile):
1085 if recovery_fstab_path not in input_file.namelist():
1086 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1087 else:
1088 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1089 if not os.path.exists(path):
1090 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1091 return LoadRecoveryFSTab(
1092 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1093 system_root_image)
1094
1095 return None
1096
1097
Doug Zongker37974732010-09-16 17:44:38 -07001098def DumpInfoDict(d):
1099 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001100 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001101
Dan Albert8b72aef2015-03-23 19:13:21 -07001102
Daniel Norman55417142019-11-25 16:04:36 -08001103def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001104 """Merges dynamic partition info variables.
1105
1106 Args:
1107 framework_dict: The dictionary of dynamic partition info variables from the
1108 partial framework target files.
1109 vendor_dict: The dictionary of dynamic partition info variables from the
1110 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001111
1112 Returns:
1113 The merged dynamic partition info dictionary.
1114 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001115
1116 def uniq_concat(a, b):
1117 combined = set(a.split(" "))
1118 combined.update(set(b.split(" ")))
1119 combined = [item.strip() for item in combined if item.strip()]
1120 return " ".join(sorted(combined))
1121
1122 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001123 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001124 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1125
1126 merged_dict = {"use_dynamic_partitions": "true"}
1127
1128 merged_dict["dynamic_partition_list"] = uniq_concat(
1129 framework_dict.get("dynamic_partition_list", ""),
1130 vendor_dict.get("dynamic_partition_list", ""))
1131
1132 # Super block devices are defined by the vendor dict.
1133 if "super_block_devices" in vendor_dict:
1134 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1135 for block_device in merged_dict["super_block_devices"].split(" "):
1136 key = "super_%s_device_size" % block_device
1137 if key not in vendor_dict:
1138 raise ValueError("Vendor dict does not contain required key %s." % key)
1139 merged_dict[key] = vendor_dict[key]
1140
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001141 # Partition groups and group sizes are defined by the vendor dict because
1142 # these values may vary for each board that uses a shared system image.
1143 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001144 for partition_group in merged_dict["super_partition_groups"].split(" "):
1145 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001146 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001147 if key not in vendor_dict:
1148 raise ValueError("Vendor dict does not contain required key %s." % key)
1149 merged_dict[key] = vendor_dict[key]
1150
1151 # Set the partition group's partition list using a concatenation of the
1152 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001153 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001154 merged_dict[key] = uniq_concat(
1155 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301156
Daniel Normanb0c75912020-09-24 14:30:21 -07001157 # Various other flags should be copied from the vendor dict, if defined.
1158 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1159 "super_metadata_device", "super_partition_error_limit",
1160 "super_partition_size"):
1161 if key in vendor_dict.keys():
1162 merged_dict[key] = vendor_dict[key]
1163
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001164 return merged_dict
1165
1166
Daniel Norman21c34f72020-11-11 17:25:50 -08001167def PartitionMapFromTargetFiles(target_files_dir):
1168 """Builds a map from partition -> path within an extracted target files directory."""
1169 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1170 possible_subdirs = {
1171 "system": ["SYSTEM"],
1172 "vendor": ["VENDOR", "SYSTEM/vendor"],
1173 "product": ["PRODUCT", "SYSTEM/product"],
1174 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1175 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1176 "vendor_dlkm": [
1177 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1178 ],
1179 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1180 }
1181 partition_map = {}
1182 for partition, subdirs in possible_subdirs.items():
1183 for subdir in subdirs:
1184 if os.path.exists(os.path.join(target_files_dir, subdir)):
1185 partition_map[partition] = subdir
1186 break
1187 return partition_map
1188
1189
Daniel Normand3351562020-10-29 12:33:11 -07001190def SharedUidPartitionViolations(uid_dict, partition_groups):
1191 """Checks for APK sharedUserIds that cross partition group boundaries.
1192
1193 This uses a single or merged build's shareduid_violation_modules.json
1194 output file, as generated by find_shareduid_violation.py or
1195 core/tasks/find-shareduid-violation.mk.
1196
1197 An error is defined as a sharedUserId that is found in a set of partitions
1198 that span more than one partition group.
1199
1200 Args:
1201 uid_dict: A dictionary created by using the standard json module to read a
1202 complete shareduid_violation_modules.json file.
1203 partition_groups: A list of groups, where each group is a list of
1204 partitions.
1205
1206 Returns:
1207 A list of error messages.
1208 """
1209 errors = []
1210 for uid, partitions in uid_dict.items():
1211 found_in_groups = [
1212 group for group in partition_groups
1213 if set(partitions.keys()) & set(group)
1214 ]
1215 if len(found_in_groups) > 1:
1216 errors.append(
1217 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1218 % (uid, ",".join(sorted(partitions.keys()))))
1219 return errors
1220
1221
Daniel Norman21c34f72020-11-11 17:25:50 -08001222def RunHostInitVerifier(product_out, partition_map):
1223 """Runs host_init_verifier on the init rc files within partitions.
1224
1225 host_init_verifier searches the etc/init path within each partition.
1226
1227 Args:
1228 product_out: PRODUCT_OUT directory, containing partition directories.
1229 partition_map: A map of partition name -> relative path within product_out.
1230 """
1231 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1232 cmd = ["host_init_verifier"]
1233 for partition, path in partition_map.items():
1234 if partition not in allowed_partitions:
1235 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1236 partition)
1237 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1238 # Add --property-contexts if the file exists on the partition.
1239 property_contexts = "%s_property_contexts" % (
1240 "plat" if partition == "system" else partition)
1241 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1242 property_contexts)
1243 if os.path.exists(property_contexts_path):
1244 cmd.append("--property-contexts=%s" % property_contexts_path)
1245 # Add the passwd file if the file exists on the partition.
1246 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1247 if os.path.exists(passwd_path):
1248 cmd.extend(["-p", passwd_path])
1249 return RunAndCheckOutput(cmd)
1250
1251
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001252def AppendAVBSigningArgs(cmd, partition):
1253 """Append signing arguments for avbtool."""
1254 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1255 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001256 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1257 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1258 if os.path.exists(new_key_path):
1259 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001260 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1261 if key_path and algorithm:
1262 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001263 avb_salt = OPTIONS.info_dict.get("avb_salt")
1264 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001265 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001266 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001267
1268
Tao Bao765668f2019-10-04 22:03:00 -07001269def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001270 """Returns the VBMeta arguments for partition.
1271
1272 It sets up the VBMeta argument by including the partition descriptor from the
1273 given 'image', or by configuring the partition as a chained partition.
1274
1275 Args:
1276 partition: The name of the partition (e.g. "system").
1277 image: The path to the partition image.
1278 info_dict: A dict returned by common.LoadInfoDict(). Will use
1279 OPTIONS.info_dict if None has been given.
1280
1281 Returns:
1282 A list of VBMeta arguments.
1283 """
1284 if info_dict is None:
1285 info_dict = OPTIONS.info_dict
1286
1287 # Check if chain partition is used.
1288 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001289 if not key_path:
1290 return ["--include_descriptors_from_image", image]
1291
1292 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1293 # into vbmeta.img. The recovery image will be configured on an independent
1294 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1295 # See details at
1296 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001297 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001298 return []
1299
1300 # Otherwise chain the partition into vbmeta.
1301 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1302 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001303
1304
Tao Bao02a08592018-07-22 12:40:45 -07001305def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1306 """Constructs and returns the arg to build or verify a chained partition.
1307
1308 Args:
1309 partition: The partition name.
1310 info_dict: The info dict to look up the key info and rollback index
1311 location.
1312 key: The key to be used for building or verifying the partition. Defaults to
1313 the key listed in info_dict.
1314
1315 Returns:
1316 A string of form "partition:rollback_index_location:key" that can be used to
1317 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001318 """
1319 if key is None:
1320 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001321 if key and not os.path.exists(key) and OPTIONS.search_path:
1322 new_key_path = os.path.join(OPTIONS.search_path, key)
1323 if os.path.exists(new_key_path):
1324 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001325 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001326 rollback_index_location = info_dict[
1327 "avb_" + partition + "_rollback_index_location"]
1328 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1329
1330
Tianjie20dd8f22020-04-19 15:51:16 -07001331def ConstructAftlMakeImageCommands(output_image):
1332 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001333
1334 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001335 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001336 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1337 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1338 'No AFTL manufacturer key provided.'
1339
1340 vbmeta_image = MakeTempFile()
1341 os.rename(output_image, vbmeta_image)
1342 build_info = BuildInfo(OPTIONS.info_dict)
1343 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001344 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001345 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001346 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001347 "--vbmeta_image_path", vbmeta_image,
1348 "--output", output_image,
1349 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001350 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001351 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1352 "--algorithm", "SHA256_RSA4096",
1353 "--padding", "4096"]
1354 if OPTIONS.aftl_signer_helper:
1355 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001356 return aftl_cmd
1357
1358
1359def AddAftlInclusionProof(output_image):
1360 """Appends the aftl inclusion proof to the vbmeta image."""
1361
1362 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001363 RunAndCheckOutput(aftl_cmd)
1364
1365 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1366 output_image, '--transparency_log_pub_keys',
1367 OPTIONS.aftl_key_path]
1368 RunAndCheckOutput(verify_cmd)
1369
1370
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001371def AppendGkiSigningArgs(cmd):
1372 """Append GKI signing arguments for mkbootimg."""
1373 # e.g., --gki_signing_key path/to/signing_key
1374 # --gki_signing_algorithm SHA256_RSA4096"
1375
1376 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1377 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1378 if not key_path:
1379 return
1380
1381 if not os.path.exists(key_path) and OPTIONS.search_path:
1382 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1383 if os.path.exists(new_key_path):
1384 key_path = new_key_path
1385
1386 # Checks key_path exists, before appending --gki_signing_* args.
1387 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001388 raise ExternalError(
1389 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001390
1391 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1392 if key_path and algorithm:
1393 cmd.extend(["--gki_signing_key", key_path,
1394 "--gki_signing_algorithm", algorithm])
1395
1396 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1397 if signature_args:
1398 cmd.extend(["--gki_signing_signature_args", signature_args])
1399
1400
Daniel Norman276f0622019-07-26 14:13:51 -07001401def BuildVBMeta(image_path, partitions, name, needed_partitions):
1402 """Creates a VBMeta image.
1403
1404 It generates the requested VBMeta image. The requested image could be for
1405 top-level or chained VBMeta image, which is determined based on the name.
1406
1407 Args:
1408 image_path: The output path for the new VBMeta image.
1409 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001410 values. Only valid partition names are accepted, as partitions listed
1411 in common.AVB_PARTITIONS and custom partitions listed in
1412 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001413 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1414 needed_partitions: Partitions whose descriptors should be included into the
1415 generated VBMeta image.
1416
1417 Raises:
1418 AssertionError: On invalid input args.
1419 """
1420 avbtool = OPTIONS.info_dict["avb_avbtool"]
1421 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1422 AppendAVBSigningArgs(cmd, name)
1423
Hongguang Chenf23364d2020-04-27 18:36:36 -07001424 custom_partitions = OPTIONS.info_dict.get(
1425 "avb_custom_images_partition_list", "").strip().split()
1426
Daniel Norman276f0622019-07-26 14:13:51 -07001427 for partition, path in partitions.items():
1428 if partition not in needed_partitions:
1429 continue
1430 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001431 partition in AVB_VBMETA_PARTITIONS or
1432 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001433 'Unknown partition: {}'.format(partition)
1434 assert os.path.exists(path), \
1435 'Failed to find {} for {}'.format(path, partition)
1436 cmd.extend(GetAvbPartitionArg(partition, path))
1437
1438 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1439 if args and args.strip():
1440 split_args = shlex.split(args)
1441 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001442 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001443 # as a path relative to source tree, which may not be available at the
1444 # same location when running this script (we have the input target_files
1445 # zip only). For such cases, we additionally scan other locations (e.g.
1446 # IMAGES/, RADIO/, etc) before bailing out.
1447 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001448 chained_image = split_args[index + 1]
1449 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001450 continue
1451 found = False
1452 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1453 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001454 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001455 if os.path.exists(alt_path):
1456 split_args[index + 1] = alt_path
1457 found = True
1458 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001459 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001460 cmd.extend(split_args)
1461
1462 RunAndCheckOutput(cmd)
1463
Tianjie Xueaed60c2020-03-12 00:33:28 -07001464 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001465 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001466 AddAftlInclusionProof(image_path)
1467
Daniel Norman276f0622019-07-26 14:13:51 -07001468
jiajia tang836f76b2021-04-02 14:48:26 +08001469def _MakeRamdisk(sourcedir, fs_config_file=None,
1470 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001471 ramdisk_img = tempfile.NamedTemporaryFile()
1472
1473 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1474 cmd = ["mkbootfs", "-f", fs_config_file,
1475 os.path.join(sourcedir, "RAMDISK")]
1476 else:
1477 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1478 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001479 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001480 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001481 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001482 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001483 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001484 else:
1485 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001486
1487 p2.wait()
1488 p1.wait()
1489 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001490 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001491
1492 return ramdisk_img
1493
1494
Steve Muckle9793cf62020-04-08 18:27:00 -07001495def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001496 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001497 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001498
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001499 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001500 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1501 we are building a two-step special image (i.e. building a recovery image to
1502 be loaded into /boot in two-step OTAs).
1503
1504 Return the image data, or None if sourcedir does not appear to contains files
1505 for building the requested image.
1506 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001507
Yifan Hong63c5ca12020-10-08 11:54:02 -07001508 if info_dict is None:
1509 info_dict = OPTIONS.info_dict
1510
Steve Muckle9793cf62020-04-08 18:27:00 -07001511 # "boot" or "recovery", without extension.
1512 partition_name = os.path.basename(sourcedir).lower()
1513
Yifan Hong63c5ca12020-10-08 11:54:02 -07001514 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001515 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001516 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1517 logger.info("Excluded kernel binary from recovery image.")
1518 else:
1519 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001520 else:
1521 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001522 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001523 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001524 return None
1525
1526 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001527 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001528
Doug Zongkereef39442009-04-02 12:14:19 -07001529 img = tempfile.NamedTemporaryFile()
1530
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001531 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001532 ramdisk_format = _GetRamdiskFormat(info_dict)
1533 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1534 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001535
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001536 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1537 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1538
Yifan Hong63c5ca12020-10-08 11:54:02 -07001539 cmd = [mkbootimg]
1540 if kernel:
1541 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001542
Benoit Fradina45a8682014-07-14 21:00:43 +02001543 fn = os.path.join(sourcedir, "second")
1544 if os.access(fn, os.F_OK):
1545 cmd.append("--second")
1546 cmd.append(fn)
1547
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001548 fn = os.path.join(sourcedir, "dtb")
1549 if os.access(fn, os.F_OK):
1550 cmd.append("--dtb")
1551 cmd.append(fn)
1552
Doug Zongker171f1cd2009-06-15 22:36:37 -07001553 fn = os.path.join(sourcedir, "cmdline")
1554 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001555 cmd.append("--cmdline")
1556 cmd.append(open(fn).read().rstrip("\n"))
1557
1558 fn = os.path.join(sourcedir, "base")
1559 if os.access(fn, os.F_OK):
1560 cmd.append("--base")
1561 cmd.append(open(fn).read().rstrip("\n"))
1562
Ying Wang4de6b5b2010-08-25 14:29:34 -07001563 fn = os.path.join(sourcedir, "pagesize")
1564 if os.access(fn, os.F_OK):
1565 cmd.append("--pagesize")
1566 cmd.append(open(fn).read().rstrip("\n"))
1567
Steve Mucklef84668e2020-03-16 19:13:46 -07001568 if partition_name == "recovery":
1569 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301570 if not args:
1571 # Fall back to "mkbootimg_args" for recovery image
1572 # in case "recovery_mkbootimg_args" is not set.
1573 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001574 else:
1575 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001576 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001577 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001578
Tao Bao76def242017-11-21 09:25:31 -08001579 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001580 if args and args.strip():
1581 cmd.extend(shlex.split(args))
1582
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001583 if has_ramdisk:
1584 cmd.extend(["--ramdisk", ramdisk_img.name])
1585
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001586 AppendGkiSigningArgs(cmd)
1587
Tao Baod95e9fd2015-03-29 23:07:41 -07001588 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001589 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001590 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001591 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001592 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001593 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001594
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001595 if partition_name == "recovery":
1596 if info_dict.get("include_recovery_dtbo") == "true":
1597 fn = os.path.join(sourcedir, "recovery_dtbo")
1598 cmd.extend(["--recovery_dtbo", fn])
1599 if info_dict.get("include_recovery_acpio") == "true":
1600 fn = os.path.join(sourcedir, "recovery_acpio")
1601 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001602
Tao Bao986ee862018-10-04 15:46:16 -07001603 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001604
Tao Bao76def242017-11-21 09:25:31 -08001605 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001606 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001607 # Hard-code the path as "/boot" for two-step special recovery image (which
1608 # will be loaded into /boot during the two-step OTA).
1609 if two_step_image:
1610 path = "/boot"
1611 else:
Tao Baobf70c312017-07-11 17:27:55 -07001612 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001613 cmd = [OPTIONS.boot_signer_path]
1614 cmd.extend(OPTIONS.boot_signer_args)
1615 cmd.extend([path, img.name,
1616 info_dict["verity_key"] + ".pk8",
1617 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001618 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001619
Tao Baod95e9fd2015-03-29 23:07:41 -07001620 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001621 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001622 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001623 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001624 # We have switched from the prebuilt futility binary to using the tool
1625 # (futility-host) built from the source. Override the setting in the old
1626 # TF.zip.
1627 futility = info_dict["futility"]
1628 if futility.startswith("prebuilts/"):
1629 futility = "futility-host"
1630 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001631 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001632 info_dict["vboot_key"] + ".vbprivk",
1633 info_dict["vboot_subkey"] + ".vbprivk",
1634 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001635 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001636 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001637
Tao Baof3282b42015-04-01 11:21:55 -07001638 # Clean up the temp files.
1639 img_unsigned.close()
1640 img_keyblock.close()
1641
David Zeuthen8fecb282017-12-01 16:24:01 -05001642 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001643 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001644 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001645 if partition_name == "recovery":
1646 part_size = info_dict["recovery_size"]
1647 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001648 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001649 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001650 "--partition_size", str(part_size), "--partition_name",
1651 partition_name]
1652 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001653 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001654 if args and args.strip():
1655 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001656 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001657
1658 img.seek(os.SEEK_SET, 0)
1659 data = img.read()
1660
1661 if has_ramdisk:
1662 ramdisk_img.close()
1663 img.close()
1664
1665 return data
1666
1667
Doug Zongkerd5131602012-08-02 14:46:42 -07001668def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001669 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001670 """Return a File object with the desired bootable image.
1671
1672 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1673 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1674 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001675
Doug Zongker55d93282011-01-25 17:03:34 -08001676 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1677 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001678 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001679 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001680
1681 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1682 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001683 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001684 return File.FromLocalFile(name, prebuilt_path)
1685
Tao Bao32fcdab2018-10-12 10:30:39 -07001686 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001687
1688 if info_dict is None:
1689 info_dict = OPTIONS.info_dict
1690
1691 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001692 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1693 # for recovery.
1694 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1695 prebuilt_name != "boot.img" or
1696 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001697
Doug Zongker6f1d0312014-08-22 08:07:12 -07001698 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001699 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001700 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001701 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001702 if data:
1703 return File(name, data)
1704 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001705
Doug Zongkereef39442009-04-02 12:14:19 -07001706
Steve Mucklee1b10862019-07-10 10:49:37 -07001707def _BuildVendorBootImage(sourcedir, info_dict=None):
1708 """Build a vendor boot image from the specified sourcedir.
1709
1710 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1711 turn them into a vendor boot image.
1712
1713 Return the image data, or None if sourcedir does not appear to contains files
1714 for building the requested image.
1715 """
1716
1717 if info_dict is None:
1718 info_dict = OPTIONS.info_dict
1719
1720 img = tempfile.NamedTemporaryFile()
1721
jiajia tang836f76b2021-04-02 14:48:26 +08001722 ramdisk_format = _GetRamdiskFormat(info_dict)
1723 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001724
1725 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1726 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1727
1728 cmd = [mkbootimg]
1729
1730 fn = os.path.join(sourcedir, "dtb")
1731 if os.access(fn, os.F_OK):
1732 cmd.append("--dtb")
1733 cmd.append(fn)
1734
1735 fn = os.path.join(sourcedir, "vendor_cmdline")
1736 if os.access(fn, os.F_OK):
1737 cmd.append("--vendor_cmdline")
1738 cmd.append(open(fn).read().rstrip("\n"))
1739
1740 fn = os.path.join(sourcedir, "base")
1741 if os.access(fn, os.F_OK):
1742 cmd.append("--base")
1743 cmd.append(open(fn).read().rstrip("\n"))
1744
1745 fn = os.path.join(sourcedir, "pagesize")
1746 if os.access(fn, os.F_OK):
1747 cmd.append("--pagesize")
1748 cmd.append(open(fn).read().rstrip("\n"))
1749
1750 args = info_dict.get("mkbootimg_args")
1751 if args and args.strip():
1752 cmd.extend(shlex.split(args))
1753
1754 args = info_dict.get("mkbootimg_version_args")
1755 if args and args.strip():
1756 cmd.extend(shlex.split(args))
1757
1758 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1759 cmd.extend(["--vendor_boot", img.name])
1760
Devin Moore50509012021-01-13 10:45:04 -08001761 fn = os.path.join(sourcedir, "vendor_bootconfig")
1762 if os.access(fn, os.F_OK):
1763 cmd.append("--vendor_bootconfig")
1764 cmd.append(fn)
1765
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001766 ramdisk_fragment_imgs = []
1767 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1768 if os.access(fn, os.F_OK):
1769 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1770 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001771 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1772 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001773 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001774 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1775 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001776 # Use prebuilt image if found, else create ramdisk from supplied files.
1777 if os.access(fn, os.F_OK):
1778 ramdisk_fragment_pathname = fn
1779 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001780 ramdisk_fragment_root = os.path.join(
1781 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001782 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1783 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001784 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1785 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1786 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1787
Steve Mucklee1b10862019-07-10 10:49:37 -07001788 RunAndCheckOutput(cmd)
1789
1790 # AVB: if enabled, calculate and add hash.
1791 if info_dict.get("avb_enable") == "true":
1792 avbtool = info_dict["avb_avbtool"]
1793 part_size = info_dict["vendor_boot_size"]
1794 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001795 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001796 AppendAVBSigningArgs(cmd, "vendor_boot")
1797 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1798 if args and args.strip():
1799 cmd.extend(shlex.split(args))
1800 RunAndCheckOutput(cmd)
1801
1802 img.seek(os.SEEK_SET, 0)
1803 data = img.read()
1804
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001805 for f in ramdisk_fragment_imgs:
1806 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001807 ramdisk_img.close()
1808 img.close()
1809
1810 return data
1811
1812
1813def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1814 info_dict=None):
1815 """Return a File object with the desired vendor boot image.
1816
1817 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1818 the source files in 'unpack_dir'/'tree_subdir'."""
1819
1820 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1821 if os.path.exists(prebuilt_path):
1822 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1823 return File.FromLocalFile(name, prebuilt_path)
1824
1825 logger.info("building image from target_files %s...", tree_subdir)
1826
1827 if info_dict is None:
1828 info_dict = OPTIONS.info_dict
1829
Kelvin Zhang0876c412020-06-23 15:06:58 -04001830 data = _BuildVendorBootImage(
1831 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001832 if data:
1833 return File(name, data)
1834 return None
1835
1836
Narayan Kamatha07bf042017-08-14 14:49:21 +01001837def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001838 """Gunzips the given gzip compressed file to a given output file."""
1839 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001840 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001841 shutil.copyfileobj(in_file, out_file)
1842
1843
Tao Bao0ff15de2019-03-20 11:26:06 -07001844def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001845 """Unzips the archive to the given directory.
1846
1847 Args:
1848 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001849 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001850 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1851 archvie. Non-matching patterns will be filtered out. If there's no match
1852 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001853 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001854 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001855 if patterns is not None:
1856 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001857 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001858 names = input_zip.namelist()
1859 filtered = [
1860 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1861
1862 # There isn't any matching files. Don't unzip anything.
1863 if not filtered:
1864 return
1865 cmd.extend(filtered)
1866
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001867 RunAndCheckOutput(cmd)
1868
1869
Doug Zongker75f17362009-12-08 13:46:44 -08001870def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001871 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001872
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001873 Args:
1874 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1875 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1876
1877 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1878 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001879
Tao Bao1c830bf2017-12-25 10:43:47 -08001880 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001881 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001882 """
Doug Zongkereef39442009-04-02 12:14:19 -07001883
Tao Bao1c830bf2017-12-25 10:43:47 -08001884 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001885 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1886 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001887 UnzipToDir(m.group(1), tmp, pattern)
1888 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001889 filename = m.group(1)
1890 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001891 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001892
Tao Baodba59ee2018-01-09 13:21:02 -08001893 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001894
1895
Yifan Hong8a66a712019-04-04 15:37:57 -07001896def GetUserImage(which, tmpdir, input_zip,
1897 info_dict=None,
1898 allow_shared_blocks=None,
1899 hashtree_info_generator=None,
1900 reset_file_map=False):
1901 """Returns an Image object suitable for passing to BlockImageDiff.
1902
1903 This function loads the specified image from the given path. If the specified
1904 image is sparse, it also performs additional processing for OTA purpose. For
1905 example, it always adds block 0 to clobbered blocks list. It also detects
1906 files that cannot be reconstructed from the block list, for whom we should
1907 avoid applying imgdiff.
1908
1909 Args:
1910 which: The partition name.
1911 tmpdir: The directory that contains the prebuilt image and block map file.
1912 input_zip: The target-files ZIP archive.
1913 info_dict: The dict to be looked up for relevant info.
1914 allow_shared_blocks: If image is sparse, whether having shared blocks is
1915 allowed. If none, it is looked up from info_dict.
1916 hashtree_info_generator: If present and image is sparse, generates the
1917 hashtree_info for this sparse image.
1918 reset_file_map: If true and image is sparse, reset file map before returning
1919 the image.
1920 Returns:
1921 A Image object. If it is a sparse image and reset_file_map is False, the
1922 image will have file_map info loaded.
1923 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001924 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001925 info_dict = LoadInfoDict(input_zip)
1926
1927 is_sparse = info_dict.get("extfs_sparse_flag")
1928
1929 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1930 # shared blocks (i.e. some blocks will show up in multiple files' block
1931 # list). We can only allocate such shared blocks to the first "owner", and
1932 # disable imgdiff for all later occurrences.
1933 if allow_shared_blocks is None:
1934 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1935
1936 if is_sparse:
1937 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1938 hashtree_info_generator)
1939 if reset_file_map:
1940 img.ResetFileMap()
1941 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001942 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001943
1944
1945def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1946 """Returns a Image object suitable for passing to BlockImageDiff.
1947
1948 This function loads the specified non-sparse image from the given path.
1949
1950 Args:
1951 which: The partition name.
1952 tmpdir: The directory that contains the prebuilt image and block map file.
1953 Returns:
1954 A Image object.
1955 """
1956 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1957 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1958
1959 # The image and map files must have been created prior to calling
1960 # ota_from_target_files.py (since LMP).
1961 assert os.path.exists(path) and os.path.exists(mappath)
1962
Tianjie Xu41976c72019-07-03 13:57:01 -07001963 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1964
Yifan Hong8a66a712019-04-04 15:37:57 -07001965
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001966def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1967 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001968 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1969
1970 This function loads the specified sparse image from the given path, and
1971 performs additional processing for OTA purpose. For example, it always adds
1972 block 0 to clobbered blocks list. It also detects files that cannot be
1973 reconstructed from the block list, for whom we should avoid applying imgdiff.
1974
1975 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001976 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001977 tmpdir: The directory that contains the prebuilt image and block map file.
1978 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001979 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001980 hashtree_info_generator: If present, generates the hashtree_info for this
1981 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001982 Returns:
1983 A SparseImage object, with file_map info loaded.
1984 """
Tao Baoc765cca2018-01-31 17:32:40 -08001985 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1986 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1987
1988 # The image and map files must have been created prior to calling
1989 # ota_from_target_files.py (since LMP).
1990 assert os.path.exists(path) and os.path.exists(mappath)
1991
1992 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1993 # it to clobbered_blocks so that it will be written to the target
1994 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1995 clobbered_blocks = "0"
1996
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001997 image = sparse_img.SparseImage(
1998 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1999 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002000
2001 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2002 # if they contain all zeros. We can't reconstruct such a file from its block
2003 # list. Tag such entries accordingly. (Bug: 65213616)
2004 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002005 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002006 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002007 continue
2008
Tom Cherryd14b8952018-08-09 14:26:00 -07002009 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2010 # filename listed in system.map may contain an additional leading slash
2011 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2012 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002013 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002014 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002015 arcname = entry.lstrip('/')
2016 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002017 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002018 else:
2019 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002020
2021 assert arcname in input_zip.namelist(), \
2022 "Failed to find the ZIP entry for {}".format(entry)
2023
Tao Baoc765cca2018-01-31 17:32:40 -08002024 info = input_zip.getinfo(arcname)
2025 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002026
2027 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002028 # image, check the original block list to determine its completeness. Note
2029 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002030 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002031 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002032
Tao Baoc765cca2018-01-31 17:32:40 -08002033 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2034 ranges.extra['incomplete'] = True
2035
2036 return image
2037
2038
Doug Zongkereef39442009-04-02 12:14:19 -07002039def GetKeyPasswords(keylist):
2040 """Given a list of keys, prompt the user to enter passwords for
2041 those which require them. Return a {key: password} dict. password
2042 will be None if the key has no password."""
2043
Doug Zongker8ce7c252009-05-22 13:34:54 -07002044 no_passwords = []
2045 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002046 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002047 devnull = open("/dev/null", "w+b")
2048 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002049 # We don't need a password for things that aren't really keys.
2050 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002051 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002052 continue
2053
T.R. Fullhart37e10522013-03-18 10:31:26 -07002054 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002055 "-inform", "DER", "-nocrypt"],
2056 stdin=devnull.fileno(),
2057 stdout=devnull.fileno(),
2058 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002059 p.communicate()
2060 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002061 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002062 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002063 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002064 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2065 "-inform", "DER", "-passin", "pass:"],
2066 stdin=devnull.fileno(),
2067 stdout=devnull.fileno(),
2068 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002069 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002070 if p.returncode == 0:
2071 # Encrypted key with empty string as password.
2072 key_passwords[k] = ''
2073 elif stderr.startswith('Error decrypting key'):
2074 # Definitely encrypted key.
2075 # It would have said "Error reading key" if it didn't parse correctly.
2076 need_passwords.append(k)
2077 else:
2078 # Potentially, a type of key that openssl doesn't understand.
2079 # We'll let the routines in signapk.jar handle it.
2080 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002081 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002082
T.R. Fullhart37e10522013-03-18 10:31:26 -07002083 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002084 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002085 return key_passwords
2086
2087
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002088def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002089 """Gets the minSdkVersion declared in the APK.
2090
changho.shin0f125362019-07-08 10:59:00 +09002091 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002092 This can be both a decimal number (API Level) or a codename.
2093
2094 Args:
2095 apk_name: The APK filename.
2096
2097 Returns:
2098 The parsed SDK version string.
2099
2100 Raises:
2101 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002102 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002103 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002104 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002105 stderr=subprocess.PIPE)
2106 stdoutdata, stderrdata = proc.communicate()
2107 if proc.returncode != 0:
2108 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002109 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002110 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002111
Tao Baof47bf0f2018-03-21 23:28:51 -07002112 for line in stdoutdata.split("\n"):
2113 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002114 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2115 if m:
2116 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002117 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002118
2119
2120def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002121 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002122
Tao Baof47bf0f2018-03-21 23:28:51 -07002123 If minSdkVersion is set to a codename, it is translated to a number using the
2124 provided map.
2125
2126 Args:
2127 apk_name: The APK filename.
2128
2129 Returns:
2130 The parsed SDK version number.
2131
2132 Raises:
2133 ExternalError: On failing to get the min SDK version number.
2134 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002135 version = GetMinSdkVersion(apk_name)
2136 try:
2137 return int(version)
2138 except ValueError:
2139 # Not a decimal number. Codename?
2140 if version in codename_to_api_level_map:
2141 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002142 raise ExternalError(
2143 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2144 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002145
2146
2147def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002148 codename_to_api_level_map=None, whole_file=False,
2149 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002150 """Sign the input_name zip/jar/apk, producing output_name. Use the
2151 given key and password (the latter may be None if the key does not
2152 have a password.
2153
Doug Zongker951495f2009-08-14 12:44:19 -07002154 If whole_file is true, use the "-w" option to SignApk to embed a
2155 signature that covers the whole file in the archive comment of the
2156 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002157
2158 min_api_level is the API Level (int) of the oldest platform this file may end
2159 up on. If not specified for an APK, the API Level is obtained by interpreting
2160 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2161
2162 codename_to_api_level_map is needed to translate the codename which may be
2163 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002164
2165 Caller may optionally specify extra args to be passed to SignApk, which
2166 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002167 """
Tao Bao76def242017-11-21 09:25:31 -08002168 if codename_to_api_level_map is None:
2169 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002170 if extra_signapk_args is None:
2171 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002172
Alex Klyubin9667b182015-12-10 13:38:50 -08002173 java_library_path = os.path.join(
2174 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2175
Tao Baoe95540e2016-11-08 12:08:53 -08002176 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2177 ["-Djava.library.path=" + java_library_path,
2178 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002179 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002180 if whole_file:
2181 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002182
2183 min_sdk_version = min_api_level
2184 if min_sdk_version is None:
2185 if not whole_file:
2186 min_sdk_version = GetMinSdkVersionInt(
2187 input_name, codename_to_api_level_map)
2188 if min_sdk_version is not None:
2189 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2190
T.R. Fullhart37e10522013-03-18 10:31:26 -07002191 cmd.extend([key + OPTIONS.public_key_suffix,
2192 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002193 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002194
Tao Bao73dd4f42018-10-04 16:25:33 -07002195 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002196 if password is not None:
2197 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002198 stdoutdata, _ = proc.communicate(password)
2199 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002200 raise ExternalError(
2201 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002202 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002203
Doug Zongkereef39442009-04-02 12:14:19 -07002204
Doug Zongker37974732010-09-16 17:44:38 -07002205def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002206 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002207
Tao Bao9dd909e2017-11-14 11:27:32 -08002208 For non-AVB images, raise exception if the data is too big. Print a warning
2209 if the data is nearing the maximum size.
2210
2211 For AVB images, the actual image size should be identical to the limit.
2212
2213 Args:
2214 data: A string that contains all the data for the partition.
2215 target: The partition name. The ".img" suffix is optional.
2216 info_dict: The dict to be looked up for relevant info.
2217 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002218 if target.endswith(".img"):
2219 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002220 mount_point = "/" + target
2221
Ying Wangf8824af2014-06-03 14:07:27 -07002222 fs_type = None
2223 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002224 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002225 if mount_point == "/userdata":
2226 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002227 p = info_dict["fstab"][mount_point]
2228 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002229 device = p.device
2230 if "/" in device:
2231 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002232 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002233 if not fs_type or not limit:
2234 return
Doug Zongkereef39442009-04-02 12:14:19 -07002235
Andrew Boie0f9aec82012-02-14 09:32:52 -08002236 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002237 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2238 # path.
2239 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2240 if size != limit:
2241 raise ExternalError(
2242 "Mismatching image size for %s: expected %d actual %d" % (
2243 target, limit, size))
2244 else:
2245 pct = float(size) * 100.0 / limit
2246 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2247 if pct >= 99.0:
2248 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002249
2250 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002251 logger.warning("\n WARNING: %s\n", msg)
2252 else:
2253 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002254
2255
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002256def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002257 """Parses the APK certs info from a given target-files zip.
2258
2259 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2260 tuple with the following elements: (1) a dictionary that maps packages to
2261 certs (based on the "certificate" and "private_key" attributes in the file;
2262 (2) a string representing the extension of compressed APKs in the target files
2263 (e.g ".gz", ".bro").
2264
2265 Args:
2266 tf_zip: The input target_files ZipFile (already open).
2267
2268 Returns:
2269 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2270 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2271 no compressed APKs.
2272 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002273 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002274 compressed_extension = None
2275
Tao Bao0f990332017-09-08 19:02:54 -07002276 # META/apkcerts.txt contains the info for _all_ the packages known at build
2277 # time. Filter out the ones that are not installed.
2278 installed_files = set()
2279 for name in tf_zip.namelist():
2280 basename = os.path.basename(name)
2281 if basename:
2282 installed_files.add(basename)
2283
Tao Baoda30cfa2017-12-01 16:19:46 -08002284 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002285 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002286 if not line:
2287 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002288 m = re.match(
2289 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002290 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2291 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002292 line)
2293 if not m:
2294 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002295
Tao Bao818ddf52018-01-05 11:17:34 -08002296 matches = m.groupdict()
2297 cert = matches["CERT"]
2298 privkey = matches["PRIVKEY"]
2299 name = matches["NAME"]
2300 this_compressed_extension = matches["COMPRESSED"]
2301
2302 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2303 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2304 if cert in SPECIAL_CERT_STRINGS and not privkey:
2305 certmap[name] = cert
2306 elif (cert.endswith(OPTIONS.public_key_suffix) and
2307 privkey.endswith(OPTIONS.private_key_suffix) and
2308 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2309 certmap[name] = cert[:-public_key_suffix_len]
2310 else:
2311 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2312
2313 if not this_compressed_extension:
2314 continue
2315
2316 # Only count the installed files.
2317 filename = name + '.' + this_compressed_extension
2318 if filename not in installed_files:
2319 continue
2320
2321 # Make sure that all the values in the compression map have the same
2322 # extension. We don't support multiple compression methods in the same
2323 # system image.
2324 if compressed_extension:
2325 if this_compressed_extension != compressed_extension:
2326 raise ValueError(
2327 "Multiple compressed extensions: {} vs {}".format(
2328 compressed_extension, this_compressed_extension))
2329 else:
2330 compressed_extension = this_compressed_extension
2331
2332 return (certmap,
2333 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002334
2335
Doug Zongkereef39442009-04-02 12:14:19 -07002336COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002337Global options
2338
2339 -p (--path) <dir>
2340 Prepend <dir>/bin to the list of places to search for binaries run by this
2341 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002342
Doug Zongker05d3dea2009-06-22 11:32:31 -07002343 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002344 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002345
Tao Bao30df8b42018-04-23 15:32:53 -07002346 -x (--extra) <key=value>
2347 Add a key/value pair to the 'extras' dict, which device-specific extension
2348 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002349
Doug Zongkereef39442009-04-02 12:14:19 -07002350 -v (--verbose)
2351 Show command lines being executed.
2352
2353 -h (--help)
2354 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002355
2356 --logfile <file>
2357 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002358"""
2359
Kelvin Zhang0876c412020-06-23 15:06:58 -04002360
Doug Zongkereef39442009-04-02 12:14:19 -07002361def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002362 print(docstring.rstrip("\n"))
2363 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002364
2365
2366def ParseOptions(argv,
2367 docstring,
2368 extra_opts="", extra_long_opts=(),
2369 extra_option_handler=None):
2370 """Parse the options in argv and return any arguments that aren't
2371 flags. docstring is the calling module's docstring, to be displayed
2372 for errors and -h. extra_opts and extra_long_opts are for flags
2373 defined by the caller, which are processed by passing them to
2374 extra_option_handler."""
2375
2376 try:
2377 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002378 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002379 ["help", "verbose", "path=", "signapk_path=",
2380 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002381 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002382 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2383 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002384 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2385 "aftl_key_path=", "aftl_manufacturer_key_path=",
2386 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002387 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002388 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002389 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002390 sys.exit(2)
2391
Doug Zongkereef39442009-04-02 12:14:19 -07002392 for o, a in opts:
2393 if o in ("-h", "--help"):
2394 Usage(docstring)
2395 sys.exit()
2396 elif o in ("-v", "--verbose"):
2397 OPTIONS.verbose = True
2398 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002399 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002400 elif o in ("--signapk_path",):
2401 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002402 elif o in ("--signapk_shared_library_path",):
2403 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002404 elif o in ("--extra_signapk_args",):
2405 OPTIONS.extra_signapk_args = shlex.split(a)
2406 elif o in ("--java_path",):
2407 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002408 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002409 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002410 elif o in ("--android_jar_path",):
2411 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002412 elif o in ("--public_key_suffix",):
2413 OPTIONS.public_key_suffix = a
2414 elif o in ("--private_key_suffix",):
2415 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002416 elif o in ("--boot_signer_path",):
2417 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002418 elif o in ("--boot_signer_args",):
2419 OPTIONS.boot_signer_args = shlex.split(a)
2420 elif o in ("--verity_signer_path",):
2421 OPTIONS.verity_signer_path = a
2422 elif o in ("--verity_signer_args",):
2423 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002424 elif o in ("--aftl_tool_path",):
2425 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002426 elif o in ("--aftl_server",):
2427 OPTIONS.aftl_server = a
2428 elif o in ("--aftl_key_path",):
2429 OPTIONS.aftl_key_path = a
2430 elif o in ("--aftl_manufacturer_key_path",):
2431 OPTIONS.aftl_manufacturer_key_path = a
2432 elif o in ("--aftl_signer_helper",):
2433 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002434 elif o in ("-s", "--device_specific"):
2435 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002436 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002437 key, value = a.split("=", 1)
2438 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002439 elif o in ("--logfile",):
2440 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002441 else:
2442 if extra_option_handler is None or not extra_option_handler(o, a):
2443 assert False, "unknown option \"%s\"" % (o,)
2444
Doug Zongker85448772014-09-09 14:59:20 -07002445 if OPTIONS.search_path:
2446 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2447 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002448
2449 return args
2450
2451
Tao Bao4c851b12016-09-19 13:54:38 -07002452def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002453 """Make a temp file and add it to the list of things to be deleted
2454 when Cleanup() is called. Return the filename."""
2455 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2456 os.close(fd)
2457 OPTIONS.tempfiles.append(fn)
2458 return fn
2459
2460
Tao Bao1c830bf2017-12-25 10:43:47 -08002461def MakeTempDir(prefix='tmp', suffix=''):
2462 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2463
2464 Returns:
2465 The absolute pathname of the new directory.
2466 """
2467 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2468 OPTIONS.tempfiles.append(dir_name)
2469 return dir_name
2470
2471
Doug Zongkereef39442009-04-02 12:14:19 -07002472def Cleanup():
2473 for i in OPTIONS.tempfiles:
2474 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002475 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002476 else:
2477 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002478 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002479
2480
2481class PasswordManager(object):
2482 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002483 self.editor = os.getenv("EDITOR")
2484 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002485
2486 def GetPasswords(self, items):
2487 """Get passwords corresponding to each string in 'items',
2488 returning a dict. (The dict may have keys in addition to the
2489 values in 'items'.)
2490
2491 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2492 user edit that file to add more needed passwords. If no editor is
2493 available, or $ANDROID_PW_FILE isn't define, prompts the user
2494 interactively in the ordinary way.
2495 """
2496
2497 current = self.ReadFile()
2498
2499 first = True
2500 while True:
2501 missing = []
2502 for i in items:
2503 if i not in current or not current[i]:
2504 missing.append(i)
2505 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002506 if not missing:
2507 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002508
2509 for i in missing:
2510 current[i] = ""
2511
2512 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002513 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002514 if sys.version_info[0] >= 3:
2515 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002516 answer = raw_input("try to edit again? [y]> ").strip()
2517 if answer and answer[0] not in 'yY':
2518 raise RuntimeError("key passwords unavailable")
2519 first = False
2520
2521 current = self.UpdateAndReadFile(current)
2522
Kelvin Zhang0876c412020-06-23 15:06:58 -04002523 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002524 """Prompt the user to enter a value (password) for each key in
2525 'current' whose value is fales. Returns a new dict with all the
2526 values.
2527 """
2528 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002529 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002530 if v:
2531 result[k] = v
2532 else:
2533 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002534 result[k] = getpass.getpass(
2535 "Enter password for %s key> " % k).strip()
2536 if result[k]:
2537 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002538 return result
2539
2540 def UpdateAndReadFile(self, current):
2541 if not self.editor or not self.pwfile:
2542 return self.PromptResult(current)
2543
2544 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002545 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002546 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2547 f.write("# (Additional spaces are harmless.)\n\n")
2548
2549 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002550 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002551 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002552 f.write("[[[ %s ]]] %s\n" % (v, k))
2553 if not v and first_line is None:
2554 # position cursor on first line with no password.
2555 first_line = i + 4
2556 f.close()
2557
Tao Bao986ee862018-10-04 15:46:16 -07002558 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002559
2560 return self.ReadFile()
2561
2562 def ReadFile(self):
2563 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002564 if self.pwfile is None:
2565 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002566 try:
2567 f = open(self.pwfile, "r")
2568 for line in f:
2569 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002570 if not line or line[0] == '#':
2571 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002572 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2573 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002574 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002575 else:
2576 result[m.group(2)] = m.group(1)
2577 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002578 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002579 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002580 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002581 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002582
2583
Dan Albert8e0178d2015-01-27 15:53:15 -08002584def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2585 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002586
2587 # http://b/18015246
2588 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2589 # for files larger than 2GiB. We can work around this by adjusting their
2590 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2591 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2592 # it isn't clear to me exactly what circumstances cause this).
2593 # `zipfile.write()` must be used directly to work around this.
2594 #
2595 # This mess can be avoided if we port to python3.
2596 saved_zip64_limit = zipfile.ZIP64_LIMIT
2597 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2598
2599 if compress_type is None:
2600 compress_type = zip_file.compression
2601 if arcname is None:
2602 arcname = filename
2603
2604 saved_stat = os.stat(filename)
2605
2606 try:
2607 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2608 # file to be zipped and reset it when we're done.
2609 os.chmod(filename, perms)
2610
2611 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002612 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2613 # intentional. zip stores datetimes in local time without a time zone
2614 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2615 # in the zip archive.
2616 local_epoch = datetime.datetime.fromtimestamp(0)
2617 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002618 os.utime(filename, (timestamp, timestamp))
2619
2620 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2621 finally:
2622 os.chmod(filename, saved_stat.st_mode)
2623 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2624 zipfile.ZIP64_LIMIT = saved_zip64_limit
2625
2626
Tao Bao58c1b962015-05-20 09:32:18 -07002627def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002628 compress_type=None):
2629 """Wrap zipfile.writestr() function to work around the zip64 limit.
2630
2631 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2632 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2633 when calling crc32(bytes).
2634
2635 But it still works fine to write a shorter string into a large zip file.
2636 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2637 when we know the string won't be too long.
2638 """
2639
2640 saved_zip64_limit = zipfile.ZIP64_LIMIT
2641 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2642
2643 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2644 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002645 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002646 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002647 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002648 else:
Tao Baof3282b42015-04-01 11:21:55 -07002649 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002650 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2651 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2652 # such a case (since
2653 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2654 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2655 # permission bits. We follow the logic in Python 3 to get consistent
2656 # behavior between using the two versions.
2657 if not zinfo.external_attr:
2658 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002659
2660 # If compress_type is given, it overrides the value in zinfo.
2661 if compress_type is not None:
2662 zinfo.compress_type = compress_type
2663
Tao Bao58c1b962015-05-20 09:32:18 -07002664 # If perms is given, it has a priority.
2665 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002666 # If perms doesn't set the file type, mark it as a regular file.
2667 if perms & 0o770000 == 0:
2668 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002669 zinfo.external_attr = perms << 16
2670
Tao Baof3282b42015-04-01 11:21:55 -07002671 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002672 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2673
Dan Albert8b72aef2015-03-23 19:13:21 -07002674 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002675 zipfile.ZIP64_LIMIT = saved_zip64_limit
2676
2677
Tao Bao89d7ab22017-12-14 17:05:33 -08002678def ZipDelete(zip_filename, entries):
2679 """Deletes entries from a ZIP file.
2680
2681 Since deleting entries from a ZIP file is not supported, it shells out to
2682 'zip -d'.
2683
2684 Args:
2685 zip_filename: The name of the ZIP file.
2686 entries: The name of the entry, or the list of names to be deleted.
2687
2688 Raises:
2689 AssertionError: In case of non-zero return from 'zip'.
2690 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002691 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002692 entries = [entries]
2693 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002694 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002695
2696
Tao Baof3282b42015-04-01 11:21:55 -07002697def ZipClose(zip_file):
2698 # http://b/18015246
2699 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2700 # central directory.
2701 saved_zip64_limit = zipfile.ZIP64_LIMIT
2702 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2703
2704 zip_file.close()
2705
2706 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002707
2708
2709class DeviceSpecificParams(object):
2710 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002711
Doug Zongker05d3dea2009-06-22 11:32:31 -07002712 def __init__(self, **kwargs):
2713 """Keyword arguments to the constructor become attributes of this
2714 object, which is passed to all functions in the device-specific
2715 module."""
Tao Bao38884282019-07-10 22:20:56 -07002716 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002717 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002718 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002719
2720 if self.module is None:
2721 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002722 if not path:
2723 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002724 try:
2725 if os.path.isdir(path):
2726 info = imp.find_module("releasetools", [path])
2727 else:
2728 d, f = os.path.split(path)
2729 b, x = os.path.splitext(f)
2730 if x == ".py":
2731 f = b
2732 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002733 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002734 self.module = imp.load_module("device_specific", *info)
2735 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002736 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002737
2738 def _DoCall(self, function_name, *args, **kwargs):
2739 """Call the named function in the device-specific module, passing
2740 the given args and kwargs. The first argument to the call will be
2741 the DeviceSpecific object itself. If there is no module, or the
2742 module does not define the function, return the value of the
2743 'default' kwarg (which itself defaults to None)."""
2744 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002745 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002746 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2747
2748 def FullOTA_Assertions(self):
2749 """Called after emitting the block of assertions at the top of a
2750 full OTA package. Implementations can add whatever additional
2751 assertions they like."""
2752 return self._DoCall("FullOTA_Assertions")
2753
Doug Zongkere5ff5902012-01-17 10:55:37 -08002754 def FullOTA_InstallBegin(self):
2755 """Called at the start of full OTA installation."""
2756 return self._DoCall("FullOTA_InstallBegin")
2757
Yifan Hong10c530d2018-12-27 17:34:18 -08002758 def FullOTA_GetBlockDifferences(self):
2759 """Called during full OTA installation and verification.
2760 Implementation should return a list of BlockDifference objects describing
2761 the update on each additional partitions.
2762 """
2763 return self._DoCall("FullOTA_GetBlockDifferences")
2764
Doug Zongker05d3dea2009-06-22 11:32:31 -07002765 def FullOTA_InstallEnd(self):
2766 """Called at the end of full OTA installation; typically this is
2767 used to install the image for the device's baseband processor."""
2768 return self._DoCall("FullOTA_InstallEnd")
2769
2770 def IncrementalOTA_Assertions(self):
2771 """Called after emitting the block of assertions at the top of an
2772 incremental OTA package. Implementations can add whatever
2773 additional assertions they like."""
2774 return self._DoCall("IncrementalOTA_Assertions")
2775
Doug Zongkere5ff5902012-01-17 10:55:37 -08002776 def IncrementalOTA_VerifyBegin(self):
2777 """Called at the start of the verification phase of incremental
2778 OTA installation; additional checks can be placed here to abort
2779 the script before any changes are made."""
2780 return self._DoCall("IncrementalOTA_VerifyBegin")
2781
Doug Zongker05d3dea2009-06-22 11:32:31 -07002782 def IncrementalOTA_VerifyEnd(self):
2783 """Called at the end of the verification phase of incremental OTA
2784 installation; additional checks can be placed here to abort the
2785 script before any changes are made."""
2786 return self._DoCall("IncrementalOTA_VerifyEnd")
2787
Doug Zongkere5ff5902012-01-17 10:55:37 -08002788 def IncrementalOTA_InstallBegin(self):
2789 """Called at the start of incremental OTA installation (after
2790 verification is complete)."""
2791 return self._DoCall("IncrementalOTA_InstallBegin")
2792
Yifan Hong10c530d2018-12-27 17:34:18 -08002793 def IncrementalOTA_GetBlockDifferences(self):
2794 """Called during incremental OTA installation and verification.
2795 Implementation should return a list of BlockDifference objects describing
2796 the update on each additional partitions.
2797 """
2798 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2799
Doug Zongker05d3dea2009-06-22 11:32:31 -07002800 def IncrementalOTA_InstallEnd(self):
2801 """Called at the end of incremental OTA installation; typically
2802 this is used to install the image for the device's baseband
2803 processor."""
2804 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002805
Tao Bao9bc6bb22015-11-09 16:58:28 -08002806 def VerifyOTA_Assertions(self):
2807 return self._DoCall("VerifyOTA_Assertions")
2808
Tao Bao76def242017-11-21 09:25:31 -08002809
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002810class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002811 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002812 self.name = name
2813 self.data = data
2814 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002815 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002816 self.sha1 = sha1(data).hexdigest()
2817
2818 @classmethod
2819 def FromLocalFile(cls, name, diskname):
2820 f = open(diskname, "rb")
2821 data = f.read()
2822 f.close()
2823 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002824
2825 def WriteToTemp(self):
2826 t = tempfile.NamedTemporaryFile()
2827 t.write(self.data)
2828 t.flush()
2829 return t
2830
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002831 def WriteToDir(self, d):
2832 with open(os.path.join(d, self.name), "wb") as fp:
2833 fp.write(self.data)
2834
Geremy Condra36bd3652014-02-06 19:45:10 -08002835 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002836 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002837
Tao Bao76def242017-11-21 09:25:31 -08002838
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002839DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002840 ".gz": "imgdiff",
2841 ".zip": ["imgdiff", "-z"],
2842 ".jar": ["imgdiff", "-z"],
2843 ".apk": ["imgdiff", "-z"],
2844 ".img": "imgdiff",
2845}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002846
Tao Bao76def242017-11-21 09:25:31 -08002847
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002848class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002849 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002850 self.tf = tf
2851 self.sf = sf
2852 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002853 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002854
2855 def ComputePatch(self):
2856 """Compute the patch (as a string of data) needed to turn sf into
2857 tf. Returns the same tuple as GetPatch()."""
2858
2859 tf = self.tf
2860 sf = self.sf
2861
Doug Zongker24cd2802012-08-14 16:36:15 -07002862 if self.diff_program:
2863 diff_program = self.diff_program
2864 else:
2865 ext = os.path.splitext(tf.name)[1]
2866 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002867
2868 ttemp = tf.WriteToTemp()
2869 stemp = sf.WriteToTemp()
2870
2871 ext = os.path.splitext(tf.name)[1]
2872
2873 try:
2874 ptemp = tempfile.NamedTemporaryFile()
2875 if isinstance(diff_program, list):
2876 cmd = copy.copy(diff_program)
2877 else:
2878 cmd = [diff_program]
2879 cmd.append(stemp.name)
2880 cmd.append(ttemp.name)
2881 cmd.append(ptemp.name)
2882 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002883 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002884
Doug Zongkerf8340082014-08-05 10:39:37 -07002885 def run():
2886 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002887 if e:
2888 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002889 th = threading.Thread(target=run)
2890 th.start()
2891 th.join(timeout=300) # 5 mins
2892 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002893 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002894 p.terminate()
2895 th.join(5)
2896 if th.is_alive():
2897 p.kill()
2898 th.join()
2899
Tianjie Xua2a9f992018-01-05 15:15:54 -08002900 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002901 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002902 self.patch = None
2903 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002904 diff = ptemp.read()
2905 finally:
2906 ptemp.close()
2907 stemp.close()
2908 ttemp.close()
2909
2910 self.patch = diff
2911 return self.tf, self.sf, self.patch
2912
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002913 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002914 """Returns a tuple of (target_file, source_file, patch_data).
2915
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002916 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002917 computing the patch failed.
2918 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002919 return self.tf, self.sf, self.patch
2920
2921
2922def ComputeDifferences(diffs):
2923 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002924 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002925
2926 # Do the largest files first, to try and reduce the long-pole effect.
2927 by_size = [(i.tf.size, i) for i in diffs]
2928 by_size.sort(reverse=True)
2929 by_size = [i[1] for i in by_size]
2930
2931 lock = threading.Lock()
2932 diff_iter = iter(by_size) # accessed under lock
2933
2934 def worker():
2935 try:
2936 lock.acquire()
2937 for d in diff_iter:
2938 lock.release()
2939 start = time.time()
2940 d.ComputePatch()
2941 dur = time.time() - start
2942 lock.acquire()
2943
2944 tf, sf, patch = d.GetPatch()
2945 if sf.name == tf.name:
2946 name = tf.name
2947 else:
2948 name = "%s (%s)" % (tf.name, sf.name)
2949 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002950 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002951 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002952 logger.info(
2953 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2954 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002955 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002956 except Exception:
2957 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002958 raise
2959
2960 # start worker threads; wait for them all to finish.
2961 threads = [threading.Thread(target=worker)
2962 for i in range(OPTIONS.worker_threads)]
2963 for th in threads:
2964 th.start()
2965 while threads:
2966 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002967
2968
Dan Albert8b72aef2015-03-23 19:13:21 -07002969class BlockDifference(object):
2970 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002971 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002972 self.tgt = tgt
2973 self.src = src
2974 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002975 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002976 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002977
Tao Baodd2a5892015-03-12 12:32:37 -07002978 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002979 version = max(
2980 int(i) for i in
2981 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002982 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002983 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002984
Tianjie Xu41976c72019-07-03 13:57:01 -07002985 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2986 version=self.version,
2987 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002988 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002989 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002990 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002991 self.touched_src_ranges = b.touched_src_ranges
2992 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002993
Yifan Hong10c530d2018-12-27 17:34:18 -08002994 # On devices with dynamic partitions, for new partitions,
2995 # src is None but OPTIONS.source_info_dict is not.
2996 if OPTIONS.source_info_dict is None:
2997 is_dynamic_build = OPTIONS.info_dict.get(
2998 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002999 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003000 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003001 is_dynamic_build = OPTIONS.source_info_dict.get(
3002 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003003 is_dynamic_source = partition in shlex.split(
3004 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003005
Yifan Hongbb2658d2019-01-25 12:30:58 -08003006 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003007 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3008
Yifan Hongbb2658d2019-01-25 12:30:58 -08003009 # For dynamic partitions builds, check partition list in both source
3010 # and target build because new partitions may be added, and existing
3011 # partitions may be removed.
3012 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3013
Yifan Hong10c530d2018-12-27 17:34:18 -08003014 if is_dynamic:
3015 self.device = 'map_partition("%s")' % partition
3016 else:
3017 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003018 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3019 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003020 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003021 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3022 OPTIONS.source_info_dict)
3023 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003024
Tao Baod8d14be2016-02-04 14:26:02 -08003025 @property
3026 def required_cache(self):
3027 return self._required_cache
3028
Tao Bao76def242017-11-21 09:25:31 -08003029 def WriteScript(self, script, output_zip, progress=None,
3030 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003031 if not self.src:
3032 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003033 script.Print("Patching %s image unconditionally..." % (self.partition,))
3034 else:
3035 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003036
Dan Albert8b72aef2015-03-23 19:13:21 -07003037 if progress:
3038 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003039 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003040
3041 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003042 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003043
Tao Bao9bc6bb22015-11-09 16:58:28 -08003044 def WriteStrictVerifyScript(self, script):
3045 """Verify all the blocks in the care_map, including clobbered blocks.
3046
3047 This differs from the WriteVerifyScript() function: a) it prints different
3048 error messages; b) it doesn't allow half-way updated images to pass the
3049 verification."""
3050
3051 partition = self.partition
3052 script.Print("Verifying %s..." % (partition,))
3053 ranges = self.tgt.care_map
3054 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003055 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003056 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3057 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003058 self.device, ranges_str,
3059 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003060 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003061 script.AppendExtra("")
3062
Tao Baod522bdc2016-04-12 15:53:16 -07003063 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003064 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003065
3066 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003067 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003068 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003069
3070 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003071 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003072 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003073 ranges = self.touched_src_ranges
3074 expected_sha1 = self.touched_src_sha1
3075 else:
3076 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3077 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003078
3079 # No blocks to be checked, skipping.
3080 if not ranges:
3081 return
3082
Tao Bao5ece99d2015-05-12 11:42:31 -07003083 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003084 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003085 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003086 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3087 '"%s.patch.dat")) then' % (
3088 self.device, ranges_str, expected_sha1,
3089 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003090 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003091 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003092
Tianjie Xufc3422a2015-12-15 11:53:59 -08003093 if self.version >= 4:
3094
3095 # Bug: 21124327
3096 # When generating incrementals for the system and vendor partitions in
3097 # version 4 or newer, explicitly check the first block (which contains
3098 # the superblock) of the partition to see if it's what we expect. If
3099 # this check fails, give an explicit log message about the partition
3100 # having been remounted R/W (the most likely explanation).
3101 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003102 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003103
3104 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003105 if partition == "system":
3106 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3107 else:
3108 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003109 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003110 'ifelse (block_image_recover({device}, "{ranges}") && '
3111 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003112 'package_extract_file("{partition}.transfer.list"), '
3113 '"{partition}.new.dat", "{partition}.patch.dat"), '
3114 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003115 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003116 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003117 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003118
Tao Baodd2a5892015-03-12 12:32:37 -07003119 # Abort the OTA update. Note that the incremental OTA cannot be applied
3120 # even if it may match the checksum of the target partition.
3121 # a) If version < 3, operations like move and erase will make changes
3122 # unconditionally and damage the partition.
3123 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003124 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003125 if partition == "system":
3126 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3127 else:
3128 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3129 script.AppendExtra((
3130 'abort("E%d: %s partition has unexpected contents");\n'
3131 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003132
Yifan Hong10c530d2018-12-27 17:34:18 -08003133 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003134 partition = self.partition
3135 script.Print('Verifying the updated %s image...' % (partition,))
3136 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3137 ranges = self.tgt.care_map
3138 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003139 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003140 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003141 self.device, ranges_str,
3142 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003143
3144 # Bug: 20881595
3145 # Verify that extended blocks are really zeroed out.
3146 if self.tgt.extended:
3147 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003148 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003149 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003150 self.device, ranges_str,
3151 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003152 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003153 if partition == "system":
3154 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3155 else:
3156 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003157 script.AppendExtra(
3158 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003159 ' abort("E%d: %s partition has unexpected non-zero contents after '
3160 'OTA update");\n'
3161 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003162 else:
3163 script.Print('Verified the updated %s image.' % (partition,))
3164
Tianjie Xu209db462016-05-24 17:34:52 -07003165 if partition == "system":
3166 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3167 else:
3168 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3169
Tao Bao5fcaaef2015-06-01 13:40:49 -07003170 script.AppendExtra(
3171 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003172 ' abort("E%d: %s partition has unexpected contents after OTA '
3173 'update");\n'
3174 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003175
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003176 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003177 ZipWrite(output_zip,
3178 '{}.transfer.list'.format(self.path),
3179 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003180
Tao Bao76def242017-11-21 09:25:31 -08003181 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3182 # its size. Quailty 9 almost triples the compression time but doesn't
3183 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003184 # zip | brotli(quality 6) | brotli(quality 9)
3185 # compressed_size: 942M | 869M (~8% reduced) | 854M
3186 # compression_time: 75s | 265s | 719s
3187 # decompression_time: 15s | 25s | 25s
3188
3189 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003190 brotli_cmd = ['brotli', '--quality=6',
3191 '--output={}.new.dat.br'.format(self.path),
3192 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003193 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003194 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003195
3196 new_data_name = '{}.new.dat.br'.format(self.partition)
3197 ZipWrite(output_zip,
3198 '{}.new.dat.br'.format(self.path),
3199 new_data_name,
3200 compress_type=zipfile.ZIP_STORED)
3201 else:
3202 new_data_name = '{}.new.dat'.format(self.partition)
3203 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3204
Dan Albert8e0178d2015-01-27 15:53:15 -08003205 ZipWrite(output_zip,
3206 '{}.patch.dat'.format(self.path),
3207 '{}.patch.dat'.format(self.partition),
3208 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003209
Tianjie Xu209db462016-05-24 17:34:52 -07003210 if self.partition == "system":
3211 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3212 else:
3213 code = ErrorCode.VENDOR_UPDATE_FAILURE
3214
Yifan Hong10c530d2018-12-27 17:34:18 -08003215 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003216 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003217 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003218 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003219 device=self.device, partition=self.partition,
3220 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003221 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003222
Kelvin Zhang0876c412020-06-23 15:06:58 -04003223 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003224 data = source.ReadRangeSet(ranges)
3225 ctx = sha1()
3226
3227 for p in data:
3228 ctx.update(p)
3229
3230 return ctx.hexdigest()
3231
Kelvin Zhang0876c412020-06-23 15:06:58 -04003232 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003233 """Return the hash value for all zero blocks."""
3234 zero_block = '\x00' * 4096
3235 ctx = sha1()
3236 for _ in range(num_blocks):
3237 ctx.update(zero_block)
3238
3239 return ctx.hexdigest()
3240
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003241
Tianjie Xu41976c72019-07-03 13:57:01 -07003242# Expose these two classes to support vendor-specific scripts
3243DataImage = images.DataImage
3244EmptyImage = images.EmptyImage
3245
Tao Bao76def242017-11-21 09:25:31 -08003246
Doug Zongker96a57e72010-09-26 14:57:41 -07003247# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003248PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003249 "ext4": "EMMC",
3250 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003251 "f2fs": "EMMC",
3252 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003253}
Doug Zongker96a57e72010-09-26 14:57:41 -07003254
Kelvin Zhang0876c412020-06-23 15:06:58 -04003255
Yifan Hongbdb32012020-05-07 12:38:53 -07003256def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3257 """
3258 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3259 backwards compatibility. It aborts if the fstab entry has slotselect option
3260 (unless check_no_slot is explicitly set to False).
3261 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003262 fstab = info["fstab"]
3263 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003264 if check_no_slot:
3265 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003266 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003267 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3268 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003269 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003270
3271
Yifan Hongbdb32012020-05-07 12:38:53 -07003272def GetTypeAndDeviceExpr(mount_point, info):
3273 """
3274 Return the filesystem of the partition, and an edify expression that evaluates
3275 to the device at runtime.
3276 """
3277 fstab = info["fstab"]
3278 if fstab:
3279 p = fstab[mount_point]
3280 device_expr = '"%s"' % fstab[mount_point].device
3281 if p.slotselect:
3282 device_expr = 'add_slot_suffix(%s)' % device_expr
3283 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003284 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003285
3286
3287def GetEntryForDevice(fstab, device):
3288 """
3289 Returns:
3290 The first entry in fstab whose device is the given value.
3291 """
3292 if not fstab:
3293 return None
3294 for mount_point in fstab:
3295 if fstab[mount_point].device == device:
3296 return fstab[mount_point]
3297 return None
3298
Kelvin Zhang0876c412020-06-23 15:06:58 -04003299
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003300def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003301 """Parses and converts a PEM-encoded certificate into DER-encoded.
3302
3303 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3304
3305 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003306 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003307 """
3308 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003309 save = False
3310 for line in data.split("\n"):
3311 if "--END CERTIFICATE--" in line:
3312 break
3313 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003314 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003315 if "--BEGIN CERTIFICATE--" in line:
3316 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003317 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003318 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003319
Tao Bao04e1f012018-02-04 12:13:35 -08003320
3321def ExtractPublicKey(cert):
3322 """Extracts the public key (PEM-encoded) from the given certificate file.
3323
3324 Args:
3325 cert: The certificate filename.
3326
3327 Returns:
3328 The public key string.
3329
3330 Raises:
3331 AssertionError: On non-zero return from 'openssl'.
3332 """
3333 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3334 # While openssl 1.1 writes the key into the given filename followed by '-out',
3335 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3336 # stdout instead.
3337 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3338 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3339 pubkey, stderrdata = proc.communicate()
3340 assert proc.returncode == 0, \
3341 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3342 return pubkey
3343
3344
Tao Bao1ac886e2019-06-26 11:58:22 -07003345def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003346 """Extracts the AVB public key from the given public or private key.
3347
3348 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003349 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003350 key: The input key file, which should be PEM-encoded public or private key.
3351
3352 Returns:
3353 The path to the extracted AVB public key file.
3354 """
3355 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3356 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003357 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003358 return output
3359
3360
Doug Zongker412c02f2014-02-13 10:58:24 -08003361def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3362 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003363 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003364
Tao Bao6d5d6232018-03-09 17:04:42 -08003365 Most of the space in the boot and recovery images is just the kernel, which is
3366 identical for the two, so the resulting patch should be efficient. Add it to
3367 the output zip, along with a shell script that is run from init.rc on first
3368 boot to actually do the patching and install the new recovery image.
3369
3370 Args:
3371 input_dir: The top-level input directory of the target-files.zip.
3372 output_sink: The callback function that writes the result.
3373 recovery_img: File object for the recovery image.
3374 boot_img: File objects for the boot image.
3375 info_dict: A dict returned by common.LoadInfoDict() on the input
3376 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003377 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003378 if info_dict is None:
3379 info_dict = OPTIONS.info_dict
3380
Tao Bao6d5d6232018-03-09 17:04:42 -08003381 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003382 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3383
3384 if board_uses_vendorimage:
3385 # In this case, the output sink is rooted at VENDOR
3386 recovery_img_path = "etc/recovery.img"
3387 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3388 sh_dir = "bin"
3389 else:
3390 # In this case the output sink is rooted at SYSTEM
3391 recovery_img_path = "vendor/etc/recovery.img"
3392 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3393 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003394
Tao Baof2cffbd2015-07-22 12:33:18 -07003395 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003396 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003397
3398 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003399 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003400 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003401 # With system-root-image, boot and recovery images will have mismatching
3402 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3403 # to handle such a case.
3404 if system_root_image:
3405 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003406 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003407 assert not os.path.exists(path)
3408 else:
3409 diff_program = ["imgdiff"]
3410 if os.path.exists(path):
3411 diff_program.append("-b")
3412 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003413 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003414 else:
3415 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003416
3417 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3418 _, _, patch = d.ComputePatch()
3419 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003420
Dan Albertebb19aa2015-03-27 19:11:53 -07003421 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003422 # The following GetTypeAndDevice()s need to use the path in the target
3423 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003424 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3425 check_no_slot=False)
3426 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3427 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003428 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003429 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003430
Tao Baof2cffbd2015-07-22 12:33:18 -07003431 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003432
3433 # Note that we use /vendor to refer to the recovery resources. This will
3434 # work for a separate vendor partition mounted at /vendor or a
3435 # /system/vendor subdirectory on the system partition, for which init will
3436 # create a symlink from /vendor to /system/vendor.
3437
3438 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003439if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3440 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003441 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003442 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3443 log -t recovery "Installing new recovery image: succeeded" || \\
3444 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003445else
3446 log -t recovery "Recovery image already installed"
3447fi
3448""" % {'type': recovery_type,
3449 'device': recovery_device,
3450 'sha1': recovery_img.sha1,
3451 'size': recovery_img.size}
3452 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003453 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003454if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3455 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003456 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003457 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3458 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3459 log -t recovery "Installing new recovery image: succeeded" || \\
3460 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003461else
3462 log -t recovery "Recovery image already installed"
3463fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003464""" % {'boot_size': boot_img.size,
3465 'boot_sha1': boot_img.sha1,
3466 'recovery_size': recovery_img.size,
3467 'recovery_sha1': recovery_img.sha1,
3468 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003469 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003470 'recovery_type': recovery_type,
3471 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003472 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003473
Bill Peckhame868aec2019-09-17 17:06:47 -07003474 # The install script location moved from /system/etc to /system/bin in the L
3475 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3476 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003477
Tao Bao32fcdab2018-10-12 10:30:39 -07003478 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003479
Tao Baoda30cfa2017-12-01 16:19:46 -08003480 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003481
3482
3483class DynamicPartitionUpdate(object):
3484 def __init__(self, src_group=None, tgt_group=None, progress=None,
3485 block_difference=None):
3486 self.src_group = src_group
3487 self.tgt_group = tgt_group
3488 self.progress = progress
3489 self.block_difference = block_difference
3490
3491 @property
3492 def src_size(self):
3493 if not self.block_difference:
3494 return 0
3495 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3496
3497 @property
3498 def tgt_size(self):
3499 if not self.block_difference:
3500 return 0
3501 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3502
3503 @staticmethod
3504 def _GetSparseImageSize(img):
3505 if not img:
3506 return 0
3507 return img.blocksize * img.total_blocks
3508
3509
3510class DynamicGroupUpdate(object):
3511 def __init__(self, src_size=None, tgt_size=None):
3512 # None: group does not exist. 0: no size limits.
3513 self.src_size = src_size
3514 self.tgt_size = tgt_size
3515
3516
3517class DynamicPartitionsDifference(object):
3518 def __init__(self, info_dict, block_diffs, progress_dict=None,
3519 source_info_dict=None):
3520 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003521 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003522
3523 self._remove_all_before_apply = False
3524 if source_info_dict is None:
3525 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003526 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003527
Tao Baof1113e92019-06-18 12:10:14 -07003528 block_diff_dict = collections.OrderedDict(
3529 [(e.partition, e) for e in block_diffs])
3530
Yifan Hong10c530d2018-12-27 17:34:18 -08003531 assert len(block_diff_dict) == len(block_diffs), \
3532 "Duplicated BlockDifference object for {}".format(
3533 [partition for partition, count in
3534 collections.Counter(e.partition for e in block_diffs).items()
3535 if count > 1])
3536
Yifan Hong79997e52019-01-23 16:56:19 -08003537 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003538
3539 for p, block_diff in block_diff_dict.items():
3540 self._partition_updates[p] = DynamicPartitionUpdate()
3541 self._partition_updates[p].block_difference = block_diff
3542
3543 for p, progress in progress_dict.items():
3544 if p in self._partition_updates:
3545 self._partition_updates[p].progress = progress
3546
3547 tgt_groups = shlex.split(info_dict.get(
3548 "super_partition_groups", "").strip())
3549 src_groups = shlex.split(source_info_dict.get(
3550 "super_partition_groups", "").strip())
3551
3552 for g in tgt_groups:
3553 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003554 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003555 assert p in self._partition_updates, \
3556 "{} is in target super_{}_partition_list but no BlockDifference " \
3557 "object is provided.".format(p, g)
3558 self._partition_updates[p].tgt_group = g
3559
3560 for g in src_groups:
3561 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003562 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003563 assert p in self._partition_updates, \
3564 "{} is in source super_{}_partition_list but no BlockDifference " \
3565 "object is provided.".format(p, g)
3566 self._partition_updates[p].src_group = g
3567
Yifan Hong45433e42019-01-18 13:55:25 -08003568 target_dynamic_partitions = set(shlex.split(info_dict.get(
3569 "dynamic_partition_list", "").strip()))
3570 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3571 if u.tgt_size)
3572 assert block_diffs_with_target == target_dynamic_partitions, \
3573 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3574 list(target_dynamic_partitions), list(block_diffs_with_target))
3575
3576 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3577 "dynamic_partition_list", "").strip()))
3578 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3579 if u.src_size)
3580 assert block_diffs_with_source == source_dynamic_partitions, \
3581 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3582 list(source_dynamic_partitions), list(block_diffs_with_source))
3583
Yifan Hong10c530d2018-12-27 17:34:18 -08003584 if self._partition_updates:
3585 logger.info("Updating dynamic partitions %s",
3586 self._partition_updates.keys())
3587
Yifan Hong79997e52019-01-23 16:56:19 -08003588 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003589
3590 for g in tgt_groups:
3591 self._group_updates[g] = DynamicGroupUpdate()
3592 self._group_updates[g].tgt_size = int(info_dict.get(
3593 "super_%s_group_size" % g, "0").strip())
3594
3595 for g in src_groups:
3596 if g not in self._group_updates:
3597 self._group_updates[g] = DynamicGroupUpdate()
3598 self._group_updates[g].src_size = int(source_info_dict.get(
3599 "super_%s_group_size" % g, "0").strip())
3600
3601 self._Compute()
3602
3603 def WriteScript(self, script, output_zip, write_verify_script=False):
3604 script.Comment('--- Start patching dynamic partitions ---')
3605 for p, u in self._partition_updates.items():
3606 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3607 script.Comment('Patch partition %s' % p)
3608 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3609 write_verify_script=False)
3610
3611 op_list_path = MakeTempFile()
3612 with open(op_list_path, 'w') as f:
3613 for line in self._op_list:
3614 f.write('{}\n'.format(line))
3615
3616 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3617
3618 script.Comment('Update dynamic partition metadata')
3619 script.AppendExtra('assert(update_dynamic_partitions('
3620 'package_extract_file("dynamic_partitions_op_list")));')
3621
3622 if write_verify_script:
3623 for p, u in self._partition_updates.items():
3624 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3625 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003626 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003627
3628 for p, u in self._partition_updates.items():
3629 if u.tgt_size and u.src_size <= u.tgt_size:
3630 script.Comment('Patch partition %s' % p)
3631 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3632 write_verify_script=write_verify_script)
3633 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003634 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003635
3636 script.Comment('--- End patching dynamic partitions ---')
3637
3638 def _Compute(self):
3639 self._op_list = list()
3640
3641 def append(line):
3642 self._op_list.append(line)
3643
3644 def comment(line):
3645 self._op_list.append("# %s" % line)
3646
3647 if self._remove_all_before_apply:
3648 comment('Remove all existing dynamic partitions and groups before '
3649 'applying full OTA')
3650 append('remove_all_groups')
3651
3652 for p, u in self._partition_updates.items():
3653 if u.src_group and not u.tgt_group:
3654 append('remove %s' % p)
3655
3656 for p, u in self._partition_updates.items():
3657 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3658 comment('Move partition %s from %s to default' % (p, u.src_group))
3659 append('move %s default' % p)
3660
3661 for p, u in self._partition_updates.items():
3662 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3663 comment('Shrink partition %s from %d to %d' %
3664 (p, u.src_size, u.tgt_size))
3665 append('resize %s %s' % (p, u.tgt_size))
3666
3667 for g, u in self._group_updates.items():
3668 if u.src_size is not None and u.tgt_size is None:
3669 append('remove_group %s' % g)
3670 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003671 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003672 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3673 append('resize_group %s %d' % (g, u.tgt_size))
3674
3675 for g, u in self._group_updates.items():
3676 if u.src_size is None and u.tgt_size is not None:
3677 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3678 append('add_group %s %d' % (g, u.tgt_size))
3679 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003680 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003681 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3682 append('resize_group %s %d' % (g, u.tgt_size))
3683
3684 for p, u in self._partition_updates.items():
3685 if u.tgt_group and not u.src_group:
3686 comment('Add partition %s to group %s' % (p, u.tgt_group))
3687 append('add %s %s' % (p, u.tgt_group))
3688
3689 for p, u in self._partition_updates.items():
3690 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003691 comment('Grow partition %s from %d to %d' %
3692 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003693 append('resize %s %d' % (p, u.tgt_size))
3694
3695 for p, u in self._partition_updates.items():
3696 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3697 comment('Move partition %s from default to %s' %
3698 (p, u.tgt_group))
3699 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003700
3701
jiajia tangf3f842b2021-03-17 21:49:44 +08003702def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003703 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003704 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003705
3706 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003707 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003708
3709 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003710 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003711 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003712 tmp_dir = MakeTempDir('boot_', suffix='.img')
3713 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003714 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3715 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003716 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3717 if not os.path.isfile(ramdisk):
3718 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3719 return None
3720 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003721 if ramdisk_format == RamdiskFormat.LZ4:
3722 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3723 elif ramdisk_format == RamdiskFormat.GZ:
3724 with open(ramdisk, 'rb') as input_stream:
3725 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003726 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3727 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003728 p2.wait()
3729 else:
3730 logger.error('Only support lz4 or minigzip ramdisk format.')
3731 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003732
3733 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3734 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3735 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3736 # the host environment.
3737 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003738 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003739
Yifan Hongc65a0542021-01-07 14:21:01 -08003740 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3741 prop_file = os.path.join(extracted_ramdisk, search_path)
3742 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003743 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003744 logger.warning(
3745 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003746
Yifan Hong7dc51172021-01-12 11:27:39 -08003747 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003748
Yifan Hong85ac5012021-01-07 14:43:46 -08003749 except ExternalError as e:
3750 logger.warning('Unable to get boot image build props: %s', e)
3751 return None
3752
3753
3754def GetBootImageTimestamp(boot_img):
3755 """
3756 Get timestamp from ramdisk within the boot image
3757
3758 Args:
3759 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3760
3761 Return:
3762 An integer that corresponds to the timestamp of the boot image, or None
3763 if file has unknown format. Raise exception if an unexpected error has
3764 occurred.
3765 """
3766 prop_file = GetBootImageBuildProp(boot_img)
3767 if not prop_file:
3768 return None
3769
3770 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3771 if props is None:
3772 return None
3773
3774 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003775 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3776 if timestamp:
3777 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003778 logger.warning(
3779 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003780 return None
3781
3782 except ExternalError as e:
3783 logger.warning('Unable to get boot image timestamp: %s', e)
3784 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003785
3786
3787def GetCareMap(which, imgname):
3788 """Returns the care_map string for the given partition.
3789
3790 Args:
3791 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3792 imgname: The filename of the image.
3793
3794 Returns:
3795 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3796 RangeSet; or None.
3797 """
3798 assert which in PARTITIONS_WITH_CARE_MAP
3799
3800 # which + "_image_size" contains the size that the actual filesystem image
3801 # resides in, which is all that needs to be verified. The additional blocks in
3802 # the image file contain verity metadata, by reading which would trigger
3803 # invalid reads.
3804 image_size = OPTIONS.info_dict.get(which + "_image_size")
3805 if not image_size:
3806 return None
3807
3808 image_blocks = int(image_size) // 4096 - 1
3809 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3810
3811 # For sparse images, we will only check the blocks that are listed in the care
3812 # map, i.e. the ones with meaningful data.
3813 if "extfs_sparse_flag" in OPTIONS.info_dict:
3814 simg = sparse_img.SparseImage(imgname)
3815 care_map_ranges = simg.care_map.intersect(
3816 rangelib.RangeSet("0-{}".format(image_blocks)))
3817
3818 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3819 # image.
3820 else:
3821 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3822
3823 return [which, care_map_ranges.to_string_raw()]
3824
3825
3826def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3827 """Generates and adds care_map.pb for a/b partition that has care_map.
3828
3829 Args:
3830 output_file: The output zip file (needs to be already open),
3831 or file path to write care_map.pb.
3832 ab_partitions: The list of A/B partitions.
3833 image_paths: A map from the partition name to the image path.
3834 """
3835 if not output_file:
3836 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3837
3838 care_map_list = []
3839 for partition in ab_partitions:
3840 partition = partition.strip()
3841 if partition not in PARTITIONS_WITH_CARE_MAP:
3842 continue
3843
3844 verity_block_device = "{}_verity_block_device".format(partition)
3845 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3846 if (verity_block_device in OPTIONS.info_dict or
3847 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3848 if partition not in image_paths:
3849 logger.warning('Potential partition with care_map missing from images: %s',
3850 partition)
3851 continue
3852 image_path = image_paths[partition]
3853 if not os.path.exists(image_path):
3854 raise ExternalError('Expected image at path {}'.format(image_path))
3855
3856 care_map = GetCareMap(partition, image_path)
3857 if not care_map:
3858 continue
3859 care_map_list += care_map
3860
3861 # adds fingerprint field to the care_map
3862 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3863 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3864 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3865 "ro.{}.build.thumbprint".format(partition)]
3866
3867 present_props = [x for x in prop_name_list if
3868 partition_props and partition_props.GetProp(x)]
3869 if not present_props:
3870 logger.warning(
3871 "fingerprint is not present for partition %s", partition)
3872 property_id, fingerprint = "unknown", "unknown"
3873 else:
3874 property_id = present_props[0]
3875 fingerprint = partition_props.GetProp(property_id)
3876 care_map_list += [property_id, fingerprint]
3877
3878 if not care_map_list:
3879 return
3880
3881 # Converts the list into proto buf message by calling care_map_generator; and
3882 # writes the result to a temp file.
3883 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3884 suffix=".txt")
3885 with open(temp_care_map_text, 'w') as text_file:
3886 text_file.write('\n'.join(care_map_list))
3887
3888 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3889 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3890 RunAndCheckOutput(care_map_gen_cmd)
3891
3892 if not isinstance(output_file, zipfile.ZipFile):
3893 shutil.copy(temp_care_map, output_file)
3894 return
3895 # output_file is a zip file
3896 care_map_path = "META/care_map.pb"
3897 if care_map_path in output_file.namelist():
3898 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3899 # replace_updated_files_list used by add_img_to_target_files
3900 if not OPTIONS.replace_updated_files_list:
3901 OPTIONS.replace_updated_files_list = []
3902 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3903 OPTIONS.replace_updated_files_list.append(care_map_path)
3904 else:
3905 ZipWrite(output_file, temp_care_map, arcname=care_map_path)