blob: 5e2a50dab6152f58491078cd582c0b42b297af7e [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
Tianjiefdda51d2021-05-05 14:46:35 -0700375 # The length of vbmeta digest to append to the fingerprint
376 _VBMETA_DIGEST_SIZE_USED = 8
377
378 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700379 """Initializes a BuildInfo instance with the given dicts.
380
381 Note that it only wraps up the given dicts, without making copies.
382
383 Arguments:
384 info_dict: The build-time info dict.
385 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
386 that it always uses the first dict to calculate the fingerprint or the
387 device name. The rest would be used for asserting OEM properties only
388 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700389 use_legacy_id: Use the legacy build id to construct the fingerprint. This
390 is used when we need a BuildInfo class, while the vbmeta digest is
391 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700392
393 Raises:
394 ValueError: On invalid inputs.
395 """
396 self.info_dict = info_dict
397 self.oem_dicts = oem_dicts
398
399 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700400 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700401
Hongguang Chend7c160f2020-05-03 21:24:26 -0700402 # Skip _oem_props if oem_dicts is None to use BuildInfo in
403 # sign_target_files_apks
404 if self.oem_dicts:
405 self._oem_props = info_dict.get("oem_fingerprint_properties")
406 else:
407 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700408
Daniel Normand5fe8622020-01-08 17:01:11 -0800409 def check_fingerprint(fingerprint):
410 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
411 raise ValueError(
412 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
413 "3.2.2. Build Parameters.".format(fingerprint))
414
Daniel Normand5fe8622020-01-08 17:01:11 -0800415 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800416 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800417 try:
418 fingerprint = self.CalculatePartitionFingerprint(partition)
419 check_fingerprint(fingerprint)
420 self._partition_fingerprints[partition] = fingerprint
421 except ExternalError:
422 continue
423 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800424 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800425 # need a fingerprint when creating the image.
426 self._partition_fingerprints[
427 "system_other"] = self._partition_fingerprints["system"]
428
Tao Bao1c320f82019-10-04 23:25:12 -0700429 # These two should be computed only after setting self._oem_props.
430 self._device = self.GetOemProperty("ro.product.device")
431 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800432 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700433
434 @property
435 def is_ab(self):
436 return self._is_ab
437
438 @property
439 def device(self):
440 return self._device
441
442 @property
443 def fingerprint(self):
444 return self._fingerprint
445
446 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400447 def is_vabc(self):
448 vendor_prop = self.info_dict.get("vendor.build.prop")
449 vabc_enabled = vendor_prop and \
450 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
451 return vabc_enabled
452
453 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700454 def oem_props(self):
455 return self._oem_props
456
457 def __getitem__(self, key):
458 return self.info_dict[key]
459
460 def __setitem__(self, key, value):
461 self.info_dict[key] = value
462
463 def get(self, key, default=None):
464 return self.info_dict.get(key, default)
465
466 def items(self):
467 return self.info_dict.items()
468
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000469 def _GetRawBuildProp(self, prop, partition):
470 prop_file = '{}.build.prop'.format(
471 partition) if partition else 'build.prop'
472 partition_props = self.info_dict.get(prop_file)
473 if not partition_props:
474 return None
475 return partition_props.GetProp(prop)
476
Daniel Normand5fe8622020-01-08 17:01:11 -0800477 def GetPartitionBuildProp(self, prop, partition):
478 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800479
480 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400481 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800482
Daniel Normand5fe8622020-01-08 17:01:11 -0800483 # If provided a partition for this property, only look within that
484 # partition's build.prop.
485 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800486 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800487 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800488 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489
490 prop_val = self._GetRawBuildProp(prop, partition)
491 if prop_val is not None:
492 return prop_val
493 raise ExternalError("couldn't find %s in %s.build.prop" %
494 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800495
Tao Bao1c320f82019-10-04 23:25:12 -0700496 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800497 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700498 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
499 return self._ResolveRoProductBuildProp(prop)
500
Tianjiefdda51d2021-05-05 14:46:35 -0700501 if prop == "ro.build.id":
502 return self._GetBuildId()
503
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000504 prop_val = self._GetRawBuildProp(prop, None)
505 if prop_val is not None:
506 return prop_val
507
508 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700509
510 def _ResolveRoProductBuildProp(self, prop):
511 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000512 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700513 if prop_val:
514 return prop_val
515
Steven Laver8e2086e2020-04-27 16:26:31 -0700516 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000517 source_order_val = self._GetRawBuildProp(
518 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700519 if source_order_val:
520 source_order = source_order_val.split(",")
521 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700522 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700523
524 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700525 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700526 raise ExternalError(
527 "Invalid ro.product.property_source_order '{}'".format(source_order))
528
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000529 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700530 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000531 "ro.product", "ro.product.{}".format(source_partition), 1)
532 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700533 if prop_val:
534 return prop_val
535
536 raise ExternalError("couldn't resolve {}".format(prop))
537
Steven Laver8e2086e2020-04-27 16:26:31 -0700538 def _GetRoProductPropsDefaultSourceOrder(self):
539 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
540 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000541 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700542 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000543 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700544 if android_version == "10":
545 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
546 # NOTE: float() conversion of android_version will have rounding error.
547 # We are checking for "9" or less, and using "< 10" is well outside of
548 # possible floating point rounding.
549 try:
550 android_version_val = float(android_version)
551 except ValueError:
552 android_version_val = 0
553 if android_version_val < 10:
554 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
555 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
556
Tianjieb37c5be2020-10-15 21:27:10 -0700557 def _GetPlatformVersion(self):
558 version_sdk = self.GetBuildProp("ro.build.version.sdk")
559 # init code switches to version_release_or_codename (see b/158483506). After
560 # API finalization, release_or_codename will be the same as release. This
561 # is the best effort to support pre-S dev stage builds.
562 if int(version_sdk) >= 30:
563 try:
564 return self.GetBuildProp("ro.build.version.release_or_codename")
565 except ExternalError:
566 logger.warning('Failed to find ro.build.version.release_or_codename')
567
568 return self.GetBuildProp("ro.build.version.release")
569
Tianjiefdda51d2021-05-05 14:46:35 -0700570 def _GetBuildId(self):
571 build_id = self._GetRawBuildProp("ro.build.id", None)
572 if build_id:
573 return build_id
574
575 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
576 if not legacy_build_id:
577 raise ExternalError("Couldn't find build id in property file")
578
579 if self.use_legacy_id:
580 return legacy_build_id
581
582 # Append the top 8 chars of vbmeta digest to the existing build id. The
583 # logic needs to match the one in init, so that OTA can deliver correctly.
584 avb_enable = self.info_dict.get("avb_enable") == "true"
585 if not avb_enable:
586 raise ExternalError("AVB isn't enabled when using legacy build id")
587
588 vbmeta_digest = self.info_dict.get("vbmeta_digest")
589 if not vbmeta_digest:
590 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
591 " id")
592 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
593 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
594
595 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
596 return legacy_build_id + '.' + digest_prefix
597
Tianjieb37c5be2020-10-15 21:27:10 -0700598 def _GetPartitionPlatformVersion(self, partition):
599 try:
600 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
601 partition)
602 except ExternalError:
603 return self.GetPartitionBuildProp("ro.build.version.release",
604 partition)
605
Tao Bao1c320f82019-10-04 23:25:12 -0700606 def GetOemProperty(self, key):
607 if self.oem_props is not None and key in self.oem_props:
608 return self.oem_dicts[0][key]
609 return self.GetBuildProp(key)
610
Daniel Normand5fe8622020-01-08 17:01:11 -0800611 def GetPartitionFingerprint(self, partition):
612 return self._partition_fingerprints.get(partition, None)
613
614 def CalculatePartitionFingerprint(self, partition):
615 try:
616 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
617 except ExternalError:
618 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
619 self.GetPartitionBuildProp("ro.product.brand", partition),
620 self.GetPartitionBuildProp("ro.product.name", partition),
621 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700622 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800623 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400624 self.GetPartitionBuildProp(
625 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800626 self.GetPartitionBuildProp("ro.build.type", partition),
627 self.GetPartitionBuildProp("ro.build.tags", partition))
628
Tao Bao1c320f82019-10-04 23:25:12 -0700629 def CalculateFingerprint(self):
630 if self.oem_props is None:
631 try:
632 return self.GetBuildProp("ro.build.fingerprint")
633 except ExternalError:
634 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
635 self.GetBuildProp("ro.product.brand"),
636 self.GetBuildProp("ro.product.name"),
637 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700638 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700639 self.GetBuildProp("ro.build.id"),
640 self.GetBuildProp("ro.build.version.incremental"),
641 self.GetBuildProp("ro.build.type"),
642 self.GetBuildProp("ro.build.tags"))
643 return "%s/%s/%s:%s" % (
644 self.GetOemProperty("ro.product.brand"),
645 self.GetOemProperty("ro.product.name"),
646 self.GetOemProperty("ro.product.device"),
647 self.GetBuildProp("ro.build.thumbprint"))
648
649 def WriteMountOemScript(self, script):
650 assert self.oem_props is not None
651 recovery_mount_options = self.info_dict.get("recovery_mount_options")
652 script.Mount("/oem", recovery_mount_options)
653
654 def WriteDeviceAssertions(self, script, oem_no_mount):
655 # Read the property directly if not using OEM properties.
656 if not self.oem_props:
657 script.AssertDevice(self.device)
658 return
659
660 # Otherwise assert OEM properties.
661 if not self.oem_dicts:
662 raise ExternalError(
663 "No OEM file provided to answer expected assertions")
664
665 for prop in self.oem_props.split():
666 values = []
667 for oem_dict in self.oem_dicts:
668 if prop in oem_dict:
669 values.append(oem_dict[prop])
670 if not values:
671 raise ExternalError(
672 "The OEM file is missing the property %s" % (prop,))
673 script.AssertOemProperty(prop, values, oem_no_mount)
674
675
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000676def ReadFromInputFile(input_file, fn):
677 """Reads the contents of fn from input zipfile or directory."""
678 if isinstance(input_file, zipfile.ZipFile):
679 return input_file.read(fn).decode()
680 else:
681 path = os.path.join(input_file, *fn.split("/"))
682 try:
683 with open(path) as f:
684 return f.read()
685 except IOError as e:
686 if e.errno == errno.ENOENT:
687 raise KeyError(fn)
688
689
Yifan Hong10482a22021-01-07 14:38:41 -0800690def ExtractFromInputFile(input_file, fn):
691 """Extracts the contents of fn from input zipfile or directory into a file."""
692 if isinstance(input_file, zipfile.ZipFile):
693 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500694 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800695 f.write(input_file.read(fn))
696 return tmp_file
697 else:
698 file = os.path.join(input_file, *fn.split("/"))
699 if not os.path.exists(file):
700 raise KeyError(fn)
701 return file
702
Kelvin Zhang563750f2021-04-28 12:46:17 -0400703
jiajia tangf3f842b2021-03-17 21:49:44 +0800704class RamdiskFormat(object):
705 LZ4 = 1
706 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800707
Kelvin Zhang563750f2021-04-28 12:46:17 -0400708
jiajia tang836f76b2021-04-02 14:48:26 +0800709def _GetRamdiskFormat(info_dict):
710 if info_dict.get('lz4_ramdisks') == 'true':
711 ramdisk_format = RamdiskFormat.LZ4
712 else:
713 ramdisk_format = RamdiskFormat.GZ
714 return ramdisk_format
715
Kelvin Zhang563750f2021-04-28 12:46:17 -0400716
Tao Bao410ad8b2018-08-24 12:08:38 -0700717def LoadInfoDict(input_file, repacking=False):
718 """Loads the key/value pairs from the given input target_files.
719
Tianjiea85bdf02020-07-29 11:56:19 -0700720 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700721 checks and returns the parsed key/value pairs for to the given build. It's
722 usually called early when working on input target_files files, e.g. when
723 generating OTAs, or signing builds. Note that the function may be called
724 against an old target_files file (i.e. from past dessert releases). So the
725 property parsing needs to be backward compatible.
726
727 In a `META/misc_info.txt`, a few properties are stored as links to the files
728 in the PRODUCT_OUT directory. It works fine with the build system. However,
729 they are no longer available when (re)generating images from target_files zip.
730 When `repacking` is True, redirect these properties to the actual files in the
731 unzipped directory.
732
733 Args:
734 input_file: The input target_files file, which could be an open
735 zipfile.ZipFile instance, or a str for the dir that contains the files
736 unzipped from a target_files file.
737 repacking: Whether it's trying repack an target_files file after loading the
738 info dict (default: False). If so, it will rewrite a few loaded
739 properties (e.g. selinux_fc, root_dir) to point to the actual files in
740 target_files file. When doing repacking, `input_file` must be a dir.
741
742 Returns:
743 A dict that contains the parsed key/value pairs.
744
745 Raises:
746 AssertionError: On invalid input arguments.
747 ValueError: On malformed input values.
748 """
749 if repacking:
750 assert isinstance(input_file, str), \
751 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700752
Doug Zongkerc9253822014-02-04 12:17:58 -0800753 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000754 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800755
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700756 try:
Michael Runge6e836112014-04-15 17:40:21 -0700757 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700758 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700759 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700760
Tao Bao410ad8b2018-08-24 12:08:38 -0700761 if "recovery_api_version" not in d:
762 raise ValueError("Failed to find 'recovery_api_version'")
763 if "fstab_version" not in d:
764 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800765
Tao Bao410ad8b2018-08-24 12:08:38 -0700766 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700767 # "selinux_fc" properties should point to the file_contexts files
768 # (file_contexts.bin) under META/.
769 for key in d:
770 if key.endswith("selinux_fc"):
771 fc_basename = os.path.basename(d[key])
772 fc_config = os.path.join(input_file, "META", fc_basename)
773 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700774
Daniel Norman72c626f2019-05-13 15:58:14 -0700775 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700776
Tom Cherryd14b8952018-08-09 14:26:00 -0700777 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700778 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700779 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700780 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700781
David Anderson0ec64ac2019-12-06 12:21:18 -0800782 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700783 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700784 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800785 key_name = part_name + "_base_fs_file"
786 if key_name not in d:
787 continue
788 basename = os.path.basename(d[key_name])
789 base_fs_file = os.path.join(input_file, "META", basename)
790 if os.path.exists(base_fs_file):
791 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700792 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700793 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800794 "Failed to find %s base fs file: %s", part_name, base_fs_file)
795 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700796
Doug Zongker37974732010-09-16 17:44:38 -0700797 def makeint(key):
798 if key in d:
799 d[key] = int(d[key], 0)
800
801 makeint("recovery_api_version")
802 makeint("blocksize")
803 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700804 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700805 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700806 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700807 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800808 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700809
Steve Muckle903a1ca2020-05-07 17:32:10 -0700810 boot_images = "boot.img"
811 if "boot_images" in d:
812 boot_images = d["boot_images"]
813 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400814 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700815
Tao Bao765668f2019-10-04 22:03:00 -0700816 # Load recovery fstab if applicable.
817 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800818 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800819
Tianjie Xu861f4132018-09-12 11:49:33 -0700820 # Tries to load the build props for all partitions with care_map, including
821 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800822 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800823 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000824 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800825 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700826 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800827
Tao Bao3ed35d32019-10-07 20:48:48 -0700828 # Set up the salt (based on fingerprint) that will be used when adding AVB
829 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800830 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700831 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800832 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800833 fingerprint = build_info.GetPartitionFingerprint(partition)
834 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400835 d["avb_{}_salt".format(partition)] = sha256(
836 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700837
838 # Set the vbmeta digest if exists
839 try:
840 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
841 except KeyError:
842 pass
843
Kelvin Zhang39aea442020-08-17 11:04:25 -0400844 try:
845 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
846 except KeyError:
847 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700848 return d
849
Tao Baod1de6f32017-03-01 16:38:48 -0800850
Daniel Norman4cc9df62019-07-18 10:11:07 -0700851def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900852 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700853 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900854
Daniel Norman4cc9df62019-07-18 10:11:07 -0700855
856def LoadDictionaryFromFile(file_path):
857 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900858 return LoadDictionaryFromLines(lines)
859
860
Michael Runge6e836112014-04-15 17:40:21 -0700861def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700862 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700863 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700864 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700865 if not line or line.startswith("#"):
866 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700867 if "=" in line:
868 name, value = line.split("=", 1)
869 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700870 return d
871
Tao Baod1de6f32017-03-01 16:38:48 -0800872
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000873class PartitionBuildProps(object):
874 """The class holds the build prop of a particular partition.
875
876 This class loads the build.prop and holds the build properties for a given
877 partition. It also partially recognizes the 'import' statement in the
878 build.prop; and calculates alternative values of some specific build
879 properties during runtime.
880
881 Attributes:
882 input_file: a zipped target-file or an unzipped target-file directory.
883 partition: name of the partition.
884 props_allow_override: a list of build properties to search for the
885 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000886 build_props: a dict of build properties for the given partition.
887 prop_overrides: a set of props that are overridden by import.
888 placeholder_values: A dict of runtime variables' values to replace the
889 placeholders in the build.prop file. We expect exactly one value for
890 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800891 ramdisk_format: If name is "boot", the format of ramdisk inside the
892 boot image. Otherwise, its value is ignored.
893 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000894 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400895
Tianjie Xu9afb2212020-05-10 21:48:15 +0000896 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000897 self.input_file = input_file
898 self.partition = name
899 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000900 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000901 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000902 self.prop_overrides = set()
903 self.placeholder_values = {}
904 if placeholder_values:
905 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000906
907 @staticmethod
908 def FromDictionary(name, build_props):
909 """Constructs an instance from a build prop dictionary."""
910
911 props = PartitionBuildProps("unknown", name)
912 props.build_props = build_props.copy()
913 return props
914
915 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800916 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000917 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800918
919 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400920 data = PartitionBuildProps._ReadBootPropFile(
921 input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800922 else:
923 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
924
925 props = PartitionBuildProps(input_file, name, placeholder_values)
926 props._LoadBuildProp(data)
927 return props
928
929 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800930 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800931 """
932 Read build.prop for boot image from input_file.
933 Return empty string if not found.
934 """
935 try:
936 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
937 except KeyError:
938 logger.warning('Failed to read IMAGES/boot.img')
939 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800940 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800941 if prop_file is None:
942 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500943 with open(prop_file, "r") as f:
944 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800945
946 @staticmethod
947 def _ReadPartitionPropFile(input_file, name):
948 """
949 Read build.prop for name from input_file.
950 Return empty string if not found.
951 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000952 data = ''
953 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
954 '{}/build.prop'.format(name.upper())]:
955 try:
956 data = ReadFromInputFile(input_file, prop_file)
957 break
958 except KeyError:
959 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800960 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000961
Yifan Hong125d0b62020-09-24 17:07:03 -0700962 @staticmethod
963 def FromBuildPropFile(name, build_prop_file):
964 """Constructs an instance from a build prop file."""
965
966 props = PartitionBuildProps("unknown", name)
967 with open(build_prop_file) as f:
968 props._LoadBuildProp(f.read())
969 return props
970
Tianjie Xu9afb2212020-05-10 21:48:15 +0000971 def _LoadBuildProp(self, data):
972 for line in data.split('\n'):
973 line = line.strip()
974 if not line or line.startswith("#"):
975 continue
976 if line.startswith("import"):
977 overrides = self._ImportParser(line)
978 duplicates = self.prop_overrides.intersection(overrides.keys())
979 if duplicates:
980 raise ValueError('prop {} is overridden multiple times'.format(
981 ','.join(duplicates)))
982 self.prop_overrides = self.prop_overrides.union(overrides.keys())
983 self.build_props.update(overrides)
984 elif "=" in line:
985 name, value = line.split("=", 1)
986 if name in self.prop_overrides:
987 raise ValueError('prop {} is set again after overridden by import '
988 'statement'.format(name))
989 self.build_props[name] = value
990
991 def _ImportParser(self, line):
992 """Parses the build prop in a given import statement."""
993
994 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400995 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000996 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700997
998 if len(tokens) == 3:
999 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1000 return {}
1001
Tianjie Xu9afb2212020-05-10 21:48:15 +00001002 import_path = tokens[1]
1003 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1004 raise ValueError('Unrecognized import path {}'.format(line))
1005
1006 # We only recognize a subset of import statement that the init process
1007 # supports. And we can loose the restriction based on how the dynamic
1008 # fingerprint is used in practice. The placeholder format should be
1009 # ${placeholder}, and its value should be provided by the caller through
1010 # the placeholder_values.
1011 for prop, value in self.placeholder_values.items():
1012 prop_place_holder = '${{{}}}'.format(prop)
1013 if prop_place_holder in import_path:
1014 import_path = import_path.replace(prop_place_holder, value)
1015 if '$' in import_path:
1016 logger.info('Unresolved place holder in import path %s', import_path)
1017 return {}
1018
1019 import_path = import_path.replace('/{}'.format(self.partition),
1020 self.partition.upper())
1021 logger.info('Parsing build props override from %s', import_path)
1022
1023 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1024 d = LoadDictionaryFromLines(lines)
1025 return {key: val for key, val in d.items()
1026 if key in self.props_allow_override}
1027
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001028 def GetProp(self, prop):
1029 return self.build_props.get(prop)
1030
1031
Tianjie Xucfa86222016-03-07 16:31:19 -08001032def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1033 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001034 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001035 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001036 self.mount_point = mount_point
1037 self.fs_type = fs_type
1038 self.device = device
1039 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001040 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001041 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001042
1043 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001044 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001045 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001046 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001047 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001048
Tao Baod1de6f32017-03-01 16:38:48 -08001049 assert fstab_version == 2
1050
1051 d = {}
1052 for line in data.split("\n"):
1053 line = line.strip()
1054 if not line or line.startswith("#"):
1055 continue
1056
1057 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1058 pieces = line.split()
1059 if len(pieces) != 5:
1060 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1061
1062 # Ignore entries that are managed by vold.
1063 options = pieces[4]
1064 if "voldmanaged=" in options:
1065 continue
1066
1067 # It's a good line, parse it.
1068 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001069 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001070 options = options.split(",")
1071 for i in options:
1072 if i.startswith("length="):
1073 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001074 elif i == "slotselect":
1075 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001076 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001077 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001078 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001079
Tao Baod1de6f32017-03-01 16:38:48 -08001080 mount_flags = pieces[3]
1081 # Honor the SELinux context if present.
1082 context = None
1083 for i in mount_flags.split(","):
1084 if i.startswith("context="):
1085 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001086
Tao Baod1de6f32017-03-01 16:38:48 -08001087 mount_point = pieces[1]
1088 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001089 device=pieces[0], length=length, context=context,
1090 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001091
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001092 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001093 # system. Other areas assume system is always at "/system" so point /system
1094 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001095 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001096 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001097 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001098 return d
1099
1100
Tao Bao765668f2019-10-04 22:03:00 -07001101def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1102 """Finds the path to recovery fstab and loads its contents."""
1103 # recovery fstab is only meaningful when installing an update via recovery
1104 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001105 if info_dict.get('ab_update') == 'true' and \
1106 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001107 return None
1108
1109 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1110 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1111 # cases, since it may load the info_dict from an old build (e.g. when
1112 # generating incremental OTAs from that build).
1113 system_root_image = info_dict.get('system_root_image') == 'true'
1114 if info_dict.get('no_recovery') != 'true':
1115 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1116 if isinstance(input_file, zipfile.ZipFile):
1117 if recovery_fstab_path not in input_file.namelist():
1118 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1119 else:
1120 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1121 if not os.path.exists(path):
1122 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1123 return LoadRecoveryFSTab(
1124 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1125 system_root_image)
1126
1127 if info_dict.get('recovery_as_boot') == 'true':
1128 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1129 if isinstance(input_file, zipfile.ZipFile):
1130 if recovery_fstab_path not in input_file.namelist():
1131 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1132 else:
1133 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1134 if not os.path.exists(path):
1135 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1136 return LoadRecoveryFSTab(
1137 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1138 system_root_image)
1139
1140 return None
1141
1142
Doug Zongker37974732010-09-16 17:44:38 -07001143def DumpInfoDict(d):
1144 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001145 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001146
Dan Albert8b72aef2015-03-23 19:13:21 -07001147
Daniel Norman55417142019-11-25 16:04:36 -08001148def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001149 """Merges dynamic partition info variables.
1150
1151 Args:
1152 framework_dict: The dictionary of dynamic partition info variables from the
1153 partial framework target files.
1154 vendor_dict: The dictionary of dynamic partition info variables from the
1155 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001156
1157 Returns:
1158 The merged dynamic partition info dictionary.
1159 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001160
1161 def uniq_concat(a, b):
1162 combined = set(a.split(" "))
1163 combined.update(set(b.split(" ")))
1164 combined = [item.strip() for item in combined if item.strip()]
1165 return " ".join(sorted(combined))
1166
1167 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001168 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001169 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1170
1171 merged_dict = {"use_dynamic_partitions": "true"}
1172
1173 merged_dict["dynamic_partition_list"] = uniq_concat(
1174 framework_dict.get("dynamic_partition_list", ""),
1175 vendor_dict.get("dynamic_partition_list", ""))
1176
1177 # Super block devices are defined by the vendor dict.
1178 if "super_block_devices" in vendor_dict:
1179 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1180 for block_device in merged_dict["super_block_devices"].split(" "):
1181 key = "super_%s_device_size" % block_device
1182 if key not in vendor_dict:
1183 raise ValueError("Vendor dict does not contain required key %s." % key)
1184 merged_dict[key] = vendor_dict[key]
1185
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001186 # Partition groups and group sizes are defined by the vendor dict because
1187 # these values may vary for each board that uses a shared system image.
1188 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001189 for partition_group in merged_dict["super_partition_groups"].split(" "):
1190 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001191 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001192 if key not in vendor_dict:
1193 raise ValueError("Vendor dict does not contain required key %s." % key)
1194 merged_dict[key] = vendor_dict[key]
1195
1196 # Set the partition group's partition list using a concatenation of the
1197 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001198 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001199 merged_dict[key] = uniq_concat(
1200 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301201
Daniel Normanb0c75912020-09-24 14:30:21 -07001202 # Various other flags should be copied from the vendor dict, if defined.
1203 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1204 "super_metadata_device", "super_partition_error_limit",
1205 "super_partition_size"):
1206 if key in vendor_dict.keys():
1207 merged_dict[key] = vendor_dict[key]
1208
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001209 return merged_dict
1210
1211
Daniel Norman21c34f72020-11-11 17:25:50 -08001212def PartitionMapFromTargetFiles(target_files_dir):
1213 """Builds a map from partition -> path within an extracted target files directory."""
1214 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1215 possible_subdirs = {
1216 "system": ["SYSTEM"],
1217 "vendor": ["VENDOR", "SYSTEM/vendor"],
1218 "product": ["PRODUCT", "SYSTEM/product"],
1219 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1220 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1221 "vendor_dlkm": [
1222 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1223 ],
1224 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1225 }
1226 partition_map = {}
1227 for partition, subdirs in possible_subdirs.items():
1228 for subdir in subdirs:
1229 if os.path.exists(os.path.join(target_files_dir, subdir)):
1230 partition_map[partition] = subdir
1231 break
1232 return partition_map
1233
1234
Daniel Normand3351562020-10-29 12:33:11 -07001235def SharedUidPartitionViolations(uid_dict, partition_groups):
1236 """Checks for APK sharedUserIds that cross partition group boundaries.
1237
1238 This uses a single or merged build's shareduid_violation_modules.json
1239 output file, as generated by find_shareduid_violation.py or
1240 core/tasks/find-shareduid-violation.mk.
1241
1242 An error is defined as a sharedUserId that is found in a set of partitions
1243 that span more than one partition group.
1244
1245 Args:
1246 uid_dict: A dictionary created by using the standard json module to read a
1247 complete shareduid_violation_modules.json file.
1248 partition_groups: A list of groups, where each group is a list of
1249 partitions.
1250
1251 Returns:
1252 A list of error messages.
1253 """
1254 errors = []
1255 for uid, partitions in uid_dict.items():
1256 found_in_groups = [
1257 group for group in partition_groups
1258 if set(partitions.keys()) & set(group)
1259 ]
1260 if len(found_in_groups) > 1:
1261 errors.append(
1262 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1263 % (uid, ",".join(sorted(partitions.keys()))))
1264 return errors
1265
1266
Daniel Norman21c34f72020-11-11 17:25:50 -08001267def RunHostInitVerifier(product_out, partition_map):
1268 """Runs host_init_verifier on the init rc files within partitions.
1269
1270 host_init_verifier searches the etc/init path within each partition.
1271
1272 Args:
1273 product_out: PRODUCT_OUT directory, containing partition directories.
1274 partition_map: A map of partition name -> relative path within product_out.
1275 """
1276 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1277 cmd = ["host_init_verifier"]
1278 for partition, path in partition_map.items():
1279 if partition not in allowed_partitions:
1280 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1281 partition)
1282 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1283 # Add --property-contexts if the file exists on the partition.
1284 property_contexts = "%s_property_contexts" % (
1285 "plat" if partition == "system" else partition)
1286 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1287 property_contexts)
1288 if os.path.exists(property_contexts_path):
1289 cmd.append("--property-contexts=%s" % property_contexts_path)
1290 # Add the passwd file if the file exists on the partition.
1291 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1292 if os.path.exists(passwd_path):
1293 cmd.extend(["-p", passwd_path])
1294 return RunAndCheckOutput(cmd)
1295
1296
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001297def AppendAVBSigningArgs(cmd, partition):
1298 """Append signing arguments for avbtool."""
1299 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1300 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001301 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1302 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1303 if os.path.exists(new_key_path):
1304 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001305 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1306 if key_path and algorithm:
1307 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001308 avb_salt = OPTIONS.info_dict.get("avb_salt")
1309 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001310 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001311 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001312
1313
Tao Bao765668f2019-10-04 22:03:00 -07001314def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001315 """Returns the VBMeta arguments for partition.
1316
1317 It sets up the VBMeta argument by including the partition descriptor from the
1318 given 'image', or by configuring the partition as a chained partition.
1319
1320 Args:
1321 partition: The name of the partition (e.g. "system").
1322 image: The path to the partition image.
1323 info_dict: A dict returned by common.LoadInfoDict(). Will use
1324 OPTIONS.info_dict if None has been given.
1325
1326 Returns:
1327 A list of VBMeta arguments.
1328 """
1329 if info_dict is None:
1330 info_dict = OPTIONS.info_dict
1331
1332 # Check if chain partition is used.
1333 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001334 if not key_path:
1335 return ["--include_descriptors_from_image", image]
1336
1337 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1338 # into vbmeta.img. The recovery image will be configured on an independent
1339 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1340 # See details at
1341 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001342 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001343 return []
1344
1345 # Otherwise chain the partition into vbmeta.
1346 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1347 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001348
1349
Tao Bao02a08592018-07-22 12:40:45 -07001350def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1351 """Constructs and returns the arg to build or verify a chained partition.
1352
1353 Args:
1354 partition: The partition name.
1355 info_dict: The info dict to look up the key info and rollback index
1356 location.
1357 key: The key to be used for building or verifying the partition. Defaults to
1358 the key listed in info_dict.
1359
1360 Returns:
1361 A string of form "partition:rollback_index_location:key" that can be used to
1362 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001363 """
1364 if key is None:
1365 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001366 if key and not os.path.exists(key) and OPTIONS.search_path:
1367 new_key_path = os.path.join(OPTIONS.search_path, key)
1368 if os.path.exists(new_key_path):
1369 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001370 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001371 rollback_index_location = info_dict[
1372 "avb_" + partition + "_rollback_index_location"]
1373 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1374
1375
Tianjie20dd8f22020-04-19 15:51:16 -07001376def ConstructAftlMakeImageCommands(output_image):
1377 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001378
1379 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001380 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001381 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1382 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1383 'No AFTL manufacturer key provided.'
1384
1385 vbmeta_image = MakeTempFile()
1386 os.rename(output_image, vbmeta_image)
Tianjiefdda51d2021-05-05 14:46:35 -07001387 build_info = BuildInfo(OPTIONS.info_dict, use_legacy_id=True)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001388 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001389 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001390 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001391 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001392 "--vbmeta_image_path", vbmeta_image,
1393 "--output", output_image,
1394 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001395 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001396 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1397 "--algorithm", "SHA256_RSA4096",
1398 "--padding", "4096"]
1399 if OPTIONS.aftl_signer_helper:
1400 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001401 return aftl_cmd
1402
1403
1404def AddAftlInclusionProof(output_image):
1405 """Appends the aftl inclusion proof to the vbmeta image."""
1406
1407 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001408 RunAndCheckOutput(aftl_cmd)
1409
1410 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1411 output_image, '--transparency_log_pub_keys',
1412 OPTIONS.aftl_key_path]
1413 RunAndCheckOutput(verify_cmd)
1414
1415
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001416def AppendGkiSigningArgs(cmd):
1417 """Append GKI signing arguments for mkbootimg."""
1418 # e.g., --gki_signing_key path/to/signing_key
1419 # --gki_signing_algorithm SHA256_RSA4096"
1420
1421 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1422 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1423 if not key_path:
1424 return
1425
1426 if not os.path.exists(key_path) and OPTIONS.search_path:
1427 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1428 if os.path.exists(new_key_path):
1429 key_path = new_key_path
1430
1431 # Checks key_path exists, before appending --gki_signing_* args.
1432 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001433 raise ExternalError(
1434 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001435
1436 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1437 if key_path and algorithm:
1438 cmd.extend(["--gki_signing_key", key_path,
1439 "--gki_signing_algorithm", algorithm])
1440
1441 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1442 if signature_args:
1443 cmd.extend(["--gki_signing_signature_args", signature_args])
1444
1445
Daniel Norman276f0622019-07-26 14:13:51 -07001446def BuildVBMeta(image_path, partitions, name, needed_partitions):
1447 """Creates a VBMeta image.
1448
1449 It generates the requested VBMeta image. The requested image could be for
1450 top-level or chained VBMeta image, which is determined based on the name.
1451
1452 Args:
1453 image_path: The output path for the new VBMeta image.
1454 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001455 values. Only valid partition names are accepted, as partitions listed
1456 in common.AVB_PARTITIONS and custom partitions listed in
1457 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001458 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1459 needed_partitions: Partitions whose descriptors should be included into the
1460 generated VBMeta image.
1461
1462 Raises:
1463 AssertionError: On invalid input args.
1464 """
1465 avbtool = OPTIONS.info_dict["avb_avbtool"]
1466 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1467 AppendAVBSigningArgs(cmd, name)
1468
Hongguang Chenf23364d2020-04-27 18:36:36 -07001469 custom_partitions = OPTIONS.info_dict.get(
1470 "avb_custom_images_partition_list", "").strip().split()
1471
Daniel Norman276f0622019-07-26 14:13:51 -07001472 for partition, path in partitions.items():
1473 if partition not in needed_partitions:
1474 continue
1475 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001476 partition in AVB_VBMETA_PARTITIONS or
1477 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001478 'Unknown partition: {}'.format(partition)
1479 assert os.path.exists(path), \
1480 'Failed to find {} for {}'.format(path, partition)
1481 cmd.extend(GetAvbPartitionArg(partition, path))
1482
1483 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1484 if args and args.strip():
1485 split_args = shlex.split(args)
1486 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001487 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001488 # as a path relative to source tree, which may not be available at the
1489 # same location when running this script (we have the input target_files
1490 # zip only). For such cases, we additionally scan other locations (e.g.
1491 # IMAGES/, RADIO/, etc) before bailing out.
1492 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001493 chained_image = split_args[index + 1]
1494 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001495 continue
1496 found = False
1497 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1498 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001499 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001500 if os.path.exists(alt_path):
1501 split_args[index + 1] = alt_path
1502 found = True
1503 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001504 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001505 cmd.extend(split_args)
1506
1507 RunAndCheckOutput(cmd)
1508
Tianjie Xueaed60c2020-03-12 00:33:28 -07001509 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001510 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001511 AddAftlInclusionProof(image_path)
1512
Daniel Norman276f0622019-07-26 14:13:51 -07001513
jiajia tang836f76b2021-04-02 14:48:26 +08001514def _MakeRamdisk(sourcedir, fs_config_file=None,
1515 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001516 ramdisk_img = tempfile.NamedTemporaryFile()
1517
1518 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1519 cmd = ["mkbootfs", "-f", fs_config_file,
1520 os.path.join(sourcedir, "RAMDISK")]
1521 else:
1522 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1523 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001524 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001525 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001526 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001527 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001528 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001529 else:
1530 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001531
1532 p2.wait()
1533 p1.wait()
1534 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001535 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001536
1537 return ramdisk_img
1538
1539
Steve Muckle9793cf62020-04-08 18:27:00 -07001540def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001541 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001542 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001543
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001544 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001545 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1546 we are building a two-step special image (i.e. building a recovery image to
1547 be loaded into /boot in two-step OTAs).
1548
1549 Return the image data, or None if sourcedir does not appear to contains files
1550 for building the requested image.
1551 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001552
Yifan Hong63c5ca12020-10-08 11:54:02 -07001553 if info_dict is None:
1554 info_dict = OPTIONS.info_dict
1555
Steve Muckle9793cf62020-04-08 18:27:00 -07001556 # "boot" or "recovery", without extension.
1557 partition_name = os.path.basename(sourcedir).lower()
1558
Yifan Hong63c5ca12020-10-08 11:54:02 -07001559 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001560 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001561 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1562 logger.info("Excluded kernel binary from recovery image.")
1563 else:
1564 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001565 else:
1566 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001567 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001568 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001569 return None
1570
1571 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001572 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001573
Doug Zongkereef39442009-04-02 12:14:19 -07001574 img = tempfile.NamedTemporaryFile()
1575
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001576 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001577 ramdisk_format = _GetRamdiskFormat(info_dict)
1578 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1579 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001580
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001581 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1582 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1583
Yifan Hong63c5ca12020-10-08 11:54:02 -07001584 cmd = [mkbootimg]
1585 if kernel:
1586 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001587
Benoit Fradina45a8682014-07-14 21:00:43 +02001588 fn = os.path.join(sourcedir, "second")
1589 if os.access(fn, os.F_OK):
1590 cmd.append("--second")
1591 cmd.append(fn)
1592
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001593 fn = os.path.join(sourcedir, "dtb")
1594 if os.access(fn, os.F_OK):
1595 cmd.append("--dtb")
1596 cmd.append(fn)
1597
Doug Zongker171f1cd2009-06-15 22:36:37 -07001598 fn = os.path.join(sourcedir, "cmdline")
1599 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001600 cmd.append("--cmdline")
1601 cmd.append(open(fn).read().rstrip("\n"))
1602
1603 fn = os.path.join(sourcedir, "base")
1604 if os.access(fn, os.F_OK):
1605 cmd.append("--base")
1606 cmd.append(open(fn).read().rstrip("\n"))
1607
Ying Wang4de6b5b2010-08-25 14:29:34 -07001608 fn = os.path.join(sourcedir, "pagesize")
1609 if os.access(fn, os.F_OK):
1610 cmd.append("--pagesize")
1611 cmd.append(open(fn).read().rstrip("\n"))
1612
Steve Mucklef84668e2020-03-16 19:13:46 -07001613 if partition_name == "recovery":
1614 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301615 if not args:
1616 # Fall back to "mkbootimg_args" for recovery image
1617 # in case "recovery_mkbootimg_args" is not set.
1618 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001619 else:
1620 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001621 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001622 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001623
Tao Bao76def242017-11-21 09:25:31 -08001624 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001625 if args and args.strip():
1626 cmd.extend(shlex.split(args))
1627
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001628 if has_ramdisk:
1629 cmd.extend(["--ramdisk", ramdisk_img.name])
1630
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001631 AppendGkiSigningArgs(cmd)
1632
Tao Baod95e9fd2015-03-29 23:07:41 -07001633 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001634 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001635 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001636 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001637 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001638 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001639
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001640 if partition_name == "recovery":
1641 if info_dict.get("include_recovery_dtbo") == "true":
1642 fn = os.path.join(sourcedir, "recovery_dtbo")
1643 cmd.extend(["--recovery_dtbo", fn])
1644 if info_dict.get("include_recovery_acpio") == "true":
1645 fn = os.path.join(sourcedir, "recovery_acpio")
1646 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001647
Tao Bao986ee862018-10-04 15:46:16 -07001648 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001649
Tao Bao76def242017-11-21 09:25:31 -08001650 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001651 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001652 # Hard-code the path as "/boot" for two-step special recovery image (which
1653 # will be loaded into /boot during the two-step OTA).
1654 if two_step_image:
1655 path = "/boot"
1656 else:
Tao Baobf70c312017-07-11 17:27:55 -07001657 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001658 cmd = [OPTIONS.boot_signer_path]
1659 cmd.extend(OPTIONS.boot_signer_args)
1660 cmd.extend([path, img.name,
1661 info_dict["verity_key"] + ".pk8",
1662 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001663 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001664
Tao Baod95e9fd2015-03-29 23:07:41 -07001665 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001666 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001667 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001668 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001669 # We have switched from the prebuilt futility binary to using the tool
1670 # (futility-host) built from the source. Override the setting in the old
1671 # TF.zip.
1672 futility = info_dict["futility"]
1673 if futility.startswith("prebuilts/"):
1674 futility = "futility-host"
1675 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001676 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001677 info_dict["vboot_key"] + ".vbprivk",
1678 info_dict["vboot_subkey"] + ".vbprivk",
1679 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001680 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001681 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001682
Tao Baof3282b42015-04-01 11:21:55 -07001683 # Clean up the temp files.
1684 img_unsigned.close()
1685 img_keyblock.close()
1686
David Zeuthen8fecb282017-12-01 16:24:01 -05001687 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001688 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001689 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001690 if partition_name == "recovery":
1691 part_size = info_dict["recovery_size"]
1692 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001693 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001694 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001695 "--partition_size", str(part_size), "--partition_name",
1696 partition_name]
1697 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001698 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001699 if args and args.strip():
1700 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001701 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001702
1703 img.seek(os.SEEK_SET, 0)
1704 data = img.read()
1705
1706 if has_ramdisk:
1707 ramdisk_img.close()
1708 img.close()
1709
1710 return data
1711
1712
Doug Zongkerd5131602012-08-02 14:46:42 -07001713def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001714 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001715 """Return a File object with the desired bootable image.
1716
1717 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1718 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1719 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001720
Doug Zongker55d93282011-01-25 17:03:34 -08001721 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1722 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001723 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001724 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001725
1726 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1727 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001728 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001729 return File.FromLocalFile(name, prebuilt_path)
1730
Tao Bao32fcdab2018-10-12 10:30:39 -07001731 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001732
1733 if info_dict is None:
1734 info_dict = OPTIONS.info_dict
1735
1736 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001737 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1738 # for recovery.
1739 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1740 prebuilt_name != "boot.img" or
1741 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001742
Doug Zongker6f1d0312014-08-22 08:07:12 -07001743 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001744 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001745 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001746 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001747 if data:
1748 return File(name, data)
1749 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001750
Doug Zongkereef39442009-04-02 12:14:19 -07001751
Steve Mucklee1b10862019-07-10 10:49:37 -07001752def _BuildVendorBootImage(sourcedir, info_dict=None):
1753 """Build a vendor boot image from the specified sourcedir.
1754
1755 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1756 turn them into a vendor boot image.
1757
1758 Return the image data, or None if sourcedir does not appear to contains files
1759 for building the requested image.
1760 """
1761
1762 if info_dict is None:
1763 info_dict = OPTIONS.info_dict
1764
1765 img = tempfile.NamedTemporaryFile()
1766
jiajia tang836f76b2021-04-02 14:48:26 +08001767 ramdisk_format = _GetRamdiskFormat(info_dict)
1768 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001769
1770 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1771 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1772
1773 cmd = [mkbootimg]
1774
1775 fn = os.path.join(sourcedir, "dtb")
1776 if os.access(fn, os.F_OK):
1777 cmd.append("--dtb")
1778 cmd.append(fn)
1779
1780 fn = os.path.join(sourcedir, "vendor_cmdline")
1781 if os.access(fn, os.F_OK):
1782 cmd.append("--vendor_cmdline")
1783 cmd.append(open(fn).read().rstrip("\n"))
1784
1785 fn = os.path.join(sourcedir, "base")
1786 if os.access(fn, os.F_OK):
1787 cmd.append("--base")
1788 cmd.append(open(fn).read().rstrip("\n"))
1789
1790 fn = os.path.join(sourcedir, "pagesize")
1791 if os.access(fn, os.F_OK):
1792 cmd.append("--pagesize")
1793 cmd.append(open(fn).read().rstrip("\n"))
1794
1795 args = info_dict.get("mkbootimg_args")
1796 if args and args.strip():
1797 cmd.extend(shlex.split(args))
1798
1799 args = info_dict.get("mkbootimg_version_args")
1800 if args and args.strip():
1801 cmd.extend(shlex.split(args))
1802
1803 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1804 cmd.extend(["--vendor_boot", img.name])
1805
Devin Moore50509012021-01-13 10:45:04 -08001806 fn = os.path.join(sourcedir, "vendor_bootconfig")
1807 if os.access(fn, os.F_OK):
1808 cmd.append("--vendor_bootconfig")
1809 cmd.append(fn)
1810
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001811 ramdisk_fragment_imgs = []
1812 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1813 if os.access(fn, os.F_OK):
1814 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1815 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001816 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1817 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001818 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001819 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1820 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001821 # Use prebuilt image if found, else create ramdisk from supplied files.
1822 if os.access(fn, os.F_OK):
1823 ramdisk_fragment_pathname = fn
1824 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001825 ramdisk_fragment_root = os.path.join(
1826 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001827 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1828 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001829 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1830 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1831 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1832
Steve Mucklee1b10862019-07-10 10:49:37 -07001833 RunAndCheckOutput(cmd)
1834
1835 # AVB: if enabled, calculate and add hash.
1836 if info_dict.get("avb_enable") == "true":
1837 avbtool = info_dict["avb_avbtool"]
1838 part_size = info_dict["vendor_boot_size"]
1839 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001840 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001841 AppendAVBSigningArgs(cmd, "vendor_boot")
1842 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1843 if args and args.strip():
1844 cmd.extend(shlex.split(args))
1845 RunAndCheckOutput(cmd)
1846
1847 img.seek(os.SEEK_SET, 0)
1848 data = img.read()
1849
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001850 for f in ramdisk_fragment_imgs:
1851 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001852 ramdisk_img.close()
1853 img.close()
1854
1855 return data
1856
1857
1858def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1859 info_dict=None):
1860 """Return a File object with the desired vendor boot image.
1861
1862 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1863 the source files in 'unpack_dir'/'tree_subdir'."""
1864
1865 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1866 if os.path.exists(prebuilt_path):
1867 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1868 return File.FromLocalFile(name, prebuilt_path)
1869
1870 logger.info("building image from target_files %s...", tree_subdir)
1871
1872 if info_dict is None:
1873 info_dict = OPTIONS.info_dict
1874
Kelvin Zhang0876c412020-06-23 15:06:58 -04001875 data = _BuildVendorBootImage(
1876 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001877 if data:
1878 return File(name, data)
1879 return None
1880
1881
Narayan Kamatha07bf042017-08-14 14:49:21 +01001882def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001883 """Gunzips the given gzip compressed file to a given output file."""
1884 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001885 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001886 shutil.copyfileobj(in_file, out_file)
1887
1888
Tao Bao0ff15de2019-03-20 11:26:06 -07001889def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001890 """Unzips the archive to the given directory.
1891
1892 Args:
1893 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001894 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001895 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1896 archvie. Non-matching patterns will be filtered out. If there's no match
1897 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001898 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001899 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001900 if patterns is not None:
1901 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001902 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001903 names = input_zip.namelist()
1904 filtered = [
1905 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1906
1907 # There isn't any matching files. Don't unzip anything.
1908 if not filtered:
1909 return
1910 cmd.extend(filtered)
1911
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001912 RunAndCheckOutput(cmd)
1913
1914
Doug Zongker75f17362009-12-08 13:46:44 -08001915def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001916 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001917
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001918 Args:
1919 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1920 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1921
1922 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1923 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001924
Tao Bao1c830bf2017-12-25 10:43:47 -08001925 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001926 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001927 """
Doug Zongkereef39442009-04-02 12:14:19 -07001928
Tao Bao1c830bf2017-12-25 10:43:47 -08001929 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001930 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1931 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001932 UnzipToDir(m.group(1), tmp, pattern)
1933 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001934 filename = m.group(1)
1935 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001936 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001937
Tao Baodba59ee2018-01-09 13:21:02 -08001938 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001939
1940
Yifan Hong8a66a712019-04-04 15:37:57 -07001941def GetUserImage(which, tmpdir, input_zip,
1942 info_dict=None,
1943 allow_shared_blocks=None,
1944 hashtree_info_generator=None,
1945 reset_file_map=False):
1946 """Returns an Image object suitable for passing to BlockImageDiff.
1947
1948 This function loads the specified image from the given path. If the specified
1949 image is sparse, it also performs additional processing for OTA purpose. For
1950 example, it always adds block 0 to clobbered blocks list. It also detects
1951 files that cannot be reconstructed from the block list, for whom we should
1952 avoid applying imgdiff.
1953
1954 Args:
1955 which: The partition name.
1956 tmpdir: The directory that contains the prebuilt image and block map file.
1957 input_zip: The target-files ZIP archive.
1958 info_dict: The dict to be looked up for relevant info.
1959 allow_shared_blocks: If image is sparse, whether having shared blocks is
1960 allowed. If none, it is looked up from info_dict.
1961 hashtree_info_generator: If present and image is sparse, generates the
1962 hashtree_info for this sparse image.
1963 reset_file_map: If true and image is sparse, reset file map before returning
1964 the image.
1965 Returns:
1966 A Image object. If it is a sparse image and reset_file_map is False, the
1967 image will have file_map info loaded.
1968 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001969 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001970 info_dict = LoadInfoDict(input_zip)
1971
1972 is_sparse = info_dict.get("extfs_sparse_flag")
1973
1974 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1975 # shared blocks (i.e. some blocks will show up in multiple files' block
1976 # list). We can only allocate such shared blocks to the first "owner", and
1977 # disable imgdiff for all later occurrences.
1978 if allow_shared_blocks is None:
1979 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1980
1981 if is_sparse:
1982 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1983 hashtree_info_generator)
1984 if reset_file_map:
1985 img.ResetFileMap()
1986 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001987 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001988
1989
1990def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1991 """Returns a Image object suitable for passing to BlockImageDiff.
1992
1993 This function loads the specified non-sparse image from the given path.
1994
1995 Args:
1996 which: The partition name.
1997 tmpdir: The directory that contains the prebuilt image and block map file.
1998 Returns:
1999 A Image object.
2000 """
2001 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2002 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2003
2004 # The image and map files must have been created prior to calling
2005 # ota_from_target_files.py (since LMP).
2006 assert os.path.exists(path) and os.path.exists(mappath)
2007
Tianjie Xu41976c72019-07-03 13:57:01 -07002008 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2009
Yifan Hong8a66a712019-04-04 15:37:57 -07002010
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002011def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2012 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002013 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2014
2015 This function loads the specified sparse image from the given path, and
2016 performs additional processing for OTA purpose. For example, it always adds
2017 block 0 to clobbered blocks list. It also detects files that cannot be
2018 reconstructed from the block list, for whom we should avoid applying imgdiff.
2019
2020 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002021 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002022 tmpdir: The directory that contains the prebuilt image and block map file.
2023 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002024 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002025 hashtree_info_generator: If present, generates the hashtree_info for this
2026 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002027 Returns:
2028 A SparseImage object, with file_map info loaded.
2029 """
Tao Baoc765cca2018-01-31 17:32:40 -08002030 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2031 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2032
2033 # The image and map files must have been created prior to calling
2034 # ota_from_target_files.py (since LMP).
2035 assert os.path.exists(path) and os.path.exists(mappath)
2036
2037 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2038 # it to clobbered_blocks so that it will be written to the target
2039 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2040 clobbered_blocks = "0"
2041
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002042 image = sparse_img.SparseImage(
2043 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2044 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002045
2046 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2047 # if they contain all zeros. We can't reconstruct such a file from its block
2048 # list. Tag such entries accordingly. (Bug: 65213616)
2049 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002050 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002051 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002052 continue
2053
Tom Cherryd14b8952018-08-09 14:26:00 -07002054 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2055 # filename listed in system.map may contain an additional leading slash
2056 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2057 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002058 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002059 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002060 arcname = entry.lstrip('/')
2061 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002062 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002063 else:
2064 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002065
2066 assert arcname in input_zip.namelist(), \
2067 "Failed to find the ZIP entry for {}".format(entry)
2068
Tao Baoc765cca2018-01-31 17:32:40 -08002069 info = input_zip.getinfo(arcname)
2070 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002071
2072 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002073 # image, check the original block list to determine its completeness. Note
2074 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002075 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002076 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002077
Tao Baoc765cca2018-01-31 17:32:40 -08002078 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2079 ranges.extra['incomplete'] = True
2080
2081 return image
2082
2083
Doug Zongkereef39442009-04-02 12:14:19 -07002084def GetKeyPasswords(keylist):
2085 """Given a list of keys, prompt the user to enter passwords for
2086 those which require them. Return a {key: password} dict. password
2087 will be None if the key has no password."""
2088
Doug Zongker8ce7c252009-05-22 13:34:54 -07002089 no_passwords = []
2090 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002091 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002092 devnull = open("/dev/null", "w+b")
2093 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002094 # We don't need a password for things that aren't really keys.
2095 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002096 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002097 continue
2098
T.R. Fullhart37e10522013-03-18 10:31:26 -07002099 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002100 "-inform", "DER", "-nocrypt"],
2101 stdin=devnull.fileno(),
2102 stdout=devnull.fileno(),
2103 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002104 p.communicate()
2105 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002106 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002107 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002108 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002109 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2110 "-inform", "DER", "-passin", "pass:"],
2111 stdin=devnull.fileno(),
2112 stdout=devnull.fileno(),
2113 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002114 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002115 if p.returncode == 0:
2116 # Encrypted key with empty string as password.
2117 key_passwords[k] = ''
2118 elif stderr.startswith('Error decrypting key'):
2119 # Definitely encrypted key.
2120 # It would have said "Error reading key" if it didn't parse correctly.
2121 need_passwords.append(k)
2122 else:
2123 # Potentially, a type of key that openssl doesn't understand.
2124 # We'll let the routines in signapk.jar handle it.
2125 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002126 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002127
T.R. Fullhart37e10522013-03-18 10:31:26 -07002128 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002129 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002130 return key_passwords
2131
2132
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002133def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002134 """Gets the minSdkVersion declared in the APK.
2135
changho.shin0f125362019-07-08 10:59:00 +09002136 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002137 This can be both a decimal number (API Level) or a codename.
2138
2139 Args:
2140 apk_name: The APK filename.
2141
2142 Returns:
2143 The parsed SDK version string.
2144
2145 Raises:
2146 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002147 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002148 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002149 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002150 stderr=subprocess.PIPE)
2151 stdoutdata, stderrdata = proc.communicate()
2152 if proc.returncode != 0:
2153 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002154 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002155 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002156
Tao Baof47bf0f2018-03-21 23:28:51 -07002157 for line in stdoutdata.split("\n"):
2158 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002159 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2160 if m:
2161 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002162 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002163
2164
2165def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002166 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002167
Tao Baof47bf0f2018-03-21 23:28:51 -07002168 If minSdkVersion is set to a codename, it is translated to a number using the
2169 provided map.
2170
2171 Args:
2172 apk_name: The APK filename.
2173
2174 Returns:
2175 The parsed SDK version number.
2176
2177 Raises:
2178 ExternalError: On failing to get the min SDK version number.
2179 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002180 version = GetMinSdkVersion(apk_name)
2181 try:
2182 return int(version)
2183 except ValueError:
2184 # Not a decimal number. Codename?
2185 if version in codename_to_api_level_map:
2186 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002187 raise ExternalError(
2188 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2189 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002190
2191
2192def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002193 codename_to_api_level_map=None, whole_file=False,
2194 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002195 """Sign the input_name zip/jar/apk, producing output_name. Use the
2196 given key and password (the latter may be None if the key does not
2197 have a password.
2198
Doug Zongker951495f2009-08-14 12:44:19 -07002199 If whole_file is true, use the "-w" option to SignApk to embed a
2200 signature that covers the whole file in the archive comment of the
2201 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002202
2203 min_api_level is the API Level (int) of the oldest platform this file may end
2204 up on. If not specified for an APK, the API Level is obtained by interpreting
2205 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2206
2207 codename_to_api_level_map is needed to translate the codename which may be
2208 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002209
2210 Caller may optionally specify extra args to be passed to SignApk, which
2211 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002212 """
Tao Bao76def242017-11-21 09:25:31 -08002213 if codename_to_api_level_map is None:
2214 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002215 if extra_signapk_args is None:
2216 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002217
Alex Klyubin9667b182015-12-10 13:38:50 -08002218 java_library_path = os.path.join(
2219 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2220
Tao Baoe95540e2016-11-08 12:08:53 -08002221 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2222 ["-Djava.library.path=" + java_library_path,
2223 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002224 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002225 if whole_file:
2226 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002227
2228 min_sdk_version = min_api_level
2229 if min_sdk_version is None:
2230 if not whole_file:
2231 min_sdk_version = GetMinSdkVersionInt(
2232 input_name, codename_to_api_level_map)
2233 if min_sdk_version is not None:
2234 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2235
T.R. Fullhart37e10522013-03-18 10:31:26 -07002236 cmd.extend([key + OPTIONS.public_key_suffix,
2237 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002238 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002239
Tao Bao73dd4f42018-10-04 16:25:33 -07002240 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002241 if password is not None:
2242 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002243 stdoutdata, _ = proc.communicate(password)
2244 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002245 raise ExternalError(
2246 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002247 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002248
Doug Zongkereef39442009-04-02 12:14:19 -07002249
Doug Zongker37974732010-09-16 17:44:38 -07002250def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002251 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002252
Tao Bao9dd909e2017-11-14 11:27:32 -08002253 For non-AVB images, raise exception if the data is too big. Print a warning
2254 if the data is nearing the maximum size.
2255
2256 For AVB images, the actual image size should be identical to the limit.
2257
2258 Args:
2259 data: A string that contains all the data for the partition.
2260 target: The partition name. The ".img" suffix is optional.
2261 info_dict: The dict to be looked up for relevant info.
2262 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002263 if target.endswith(".img"):
2264 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002265 mount_point = "/" + target
2266
Ying Wangf8824af2014-06-03 14:07:27 -07002267 fs_type = None
2268 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002269 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002270 if mount_point == "/userdata":
2271 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002272 p = info_dict["fstab"][mount_point]
2273 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002274 device = p.device
2275 if "/" in device:
2276 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002277 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002278 if not fs_type or not limit:
2279 return
Doug Zongkereef39442009-04-02 12:14:19 -07002280
Andrew Boie0f9aec82012-02-14 09:32:52 -08002281 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002282 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2283 # path.
2284 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2285 if size != limit:
2286 raise ExternalError(
2287 "Mismatching image size for %s: expected %d actual %d" % (
2288 target, limit, size))
2289 else:
2290 pct = float(size) * 100.0 / limit
2291 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2292 if pct >= 99.0:
2293 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002294
2295 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002296 logger.warning("\n WARNING: %s\n", msg)
2297 else:
2298 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002299
2300
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002301def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002302 """Parses the APK certs info from a given target-files zip.
2303
2304 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2305 tuple with the following elements: (1) a dictionary that maps packages to
2306 certs (based on the "certificate" and "private_key" attributes in the file;
2307 (2) a string representing the extension of compressed APKs in the target files
2308 (e.g ".gz", ".bro").
2309
2310 Args:
2311 tf_zip: The input target_files ZipFile (already open).
2312
2313 Returns:
2314 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2315 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2316 no compressed APKs.
2317 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002318 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002319 compressed_extension = None
2320
Tao Bao0f990332017-09-08 19:02:54 -07002321 # META/apkcerts.txt contains the info for _all_ the packages known at build
2322 # time. Filter out the ones that are not installed.
2323 installed_files = set()
2324 for name in tf_zip.namelist():
2325 basename = os.path.basename(name)
2326 if basename:
2327 installed_files.add(basename)
2328
Tao Baoda30cfa2017-12-01 16:19:46 -08002329 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002330 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002331 if not line:
2332 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002333 m = re.match(
2334 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002335 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2336 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002337 line)
2338 if not m:
2339 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002340
Tao Bao818ddf52018-01-05 11:17:34 -08002341 matches = m.groupdict()
2342 cert = matches["CERT"]
2343 privkey = matches["PRIVKEY"]
2344 name = matches["NAME"]
2345 this_compressed_extension = matches["COMPRESSED"]
2346
2347 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2348 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2349 if cert in SPECIAL_CERT_STRINGS and not privkey:
2350 certmap[name] = cert
2351 elif (cert.endswith(OPTIONS.public_key_suffix) and
2352 privkey.endswith(OPTIONS.private_key_suffix) and
2353 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2354 certmap[name] = cert[:-public_key_suffix_len]
2355 else:
2356 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2357
2358 if not this_compressed_extension:
2359 continue
2360
2361 # Only count the installed files.
2362 filename = name + '.' + this_compressed_extension
2363 if filename not in installed_files:
2364 continue
2365
2366 # Make sure that all the values in the compression map have the same
2367 # extension. We don't support multiple compression methods in the same
2368 # system image.
2369 if compressed_extension:
2370 if this_compressed_extension != compressed_extension:
2371 raise ValueError(
2372 "Multiple compressed extensions: {} vs {}".format(
2373 compressed_extension, this_compressed_extension))
2374 else:
2375 compressed_extension = this_compressed_extension
2376
2377 return (certmap,
2378 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002379
2380
Doug Zongkereef39442009-04-02 12:14:19 -07002381COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002382Global options
2383
2384 -p (--path) <dir>
2385 Prepend <dir>/bin to the list of places to search for binaries run by this
2386 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002387
Doug Zongker05d3dea2009-06-22 11:32:31 -07002388 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002389 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002390
Tao Bao30df8b42018-04-23 15:32:53 -07002391 -x (--extra) <key=value>
2392 Add a key/value pair to the 'extras' dict, which device-specific extension
2393 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002394
Doug Zongkereef39442009-04-02 12:14:19 -07002395 -v (--verbose)
2396 Show command lines being executed.
2397
2398 -h (--help)
2399 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002400
2401 --logfile <file>
2402 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002403"""
2404
Kelvin Zhang0876c412020-06-23 15:06:58 -04002405
Doug Zongkereef39442009-04-02 12:14:19 -07002406def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002407 print(docstring.rstrip("\n"))
2408 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002409
2410
2411def ParseOptions(argv,
2412 docstring,
2413 extra_opts="", extra_long_opts=(),
2414 extra_option_handler=None):
2415 """Parse the options in argv and return any arguments that aren't
2416 flags. docstring is the calling module's docstring, to be displayed
2417 for errors and -h. extra_opts and extra_long_opts are for flags
2418 defined by the caller, which are processed by passing them to
2419 extra_option_handler."""
2420
2421 try:
2422 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002423 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002424 ["help", "verbose", "path=", "signapk_path=",
2425 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002426 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002427 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2428 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002429 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2430 "aftl_key_path=", "aftl_manufacturer_key_path=",
2431 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002432 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002433 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002434 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002435 sys.exit(2)
2436
Doug Zongkereef39442009-04-02 12:14:19 -07002437 for o, a in opts:
2438 if o in ("-h", "--help"):
2439 Usage(docstring)
2440 sys.exit()
2441 elif o in ("-v", "--verbose"):
2442 OPTIONS.verbose = True
2443 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002444 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002445 elif o in ("--signapk_path",):
2446 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002447 elif o in ("--signapk_shared_library_path",):
2448 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002449 elif o in ("--extra_signapk_args",):
2450 OPTIONS.extra_signapk_args = shlex.split(a)
2451 elif o in ("--java_path",):
2452 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002453 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002454 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002455 elif o in ("--android_jar_path",):
2456 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002457 elif o in ("--public_key_suffix",):
2458 OPTIONS.public_key_suffix = a
2459 elif o in ("--private_key_suffix",):
2460 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002461 elif o in ("--boot_signer_path",):
2462 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002463 elif o in ("--boot_signer_args",):
2464 OPTIONS.boot_signer_args = shlex.split(a)
2465 elif o in ("--verity_signer_path",):
2466 OPTIONS.verity_signer_path = a
2467 elif o in ("--verity_signer_args",):
2468 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002469 elif o in ("--aftl_tool_path",):
2470 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002471 elif o in ("--aftl_server",):
2472 OPTIONS.aftl_server = a
2473 elif o in ("--aftl_key_path",):
2474 OPTIONS.aftl_key_path = a
2475 elif o in ("--aftl_manufacturer_key_path",):
2476 OPTIONS.aftl_manufacturer_key_path = a
2477 elif o in ("--aftl_signer_helper",):
2478 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002479 elif o in ("-s", "--device_specific"):
2480 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002481 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002482 key, value = a.split("=", 1)
2483 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002484 elif o in ("--logfile",):
2485 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002486 else:
2487 if extra_option_handler is None or not extra_option_handler(o, a):
2488 assert False, "unknown option \"%s\"" % (o,)
2489
Doug Zongker85448772014-09-09 14:59:20 -07002490 if OPTIONS.search_path:
2491 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2492 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002493
2494 return args
2495
2496
Tao Bao4c851b12016-09-19 13:54:38 -07002497def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002498 """Make a temp file and add it to the list of things to be deleted
2499 when Cleanup() is called. Return the filename."""
2500 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2501 os.close(fd)
2502 OPTIONS.tempfiles.append(fn)
2503 return fn
2504
2505
Tao Bao1c830bf2017-12-25 10:43:47 -08002506def MakeTempDir(prefix='tmp', suffix=''):
2507 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2508
2509 Returns:
2510 The absolute pathname of the new directory.
2511 """
2512 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2513 OPTIONS.tempfiles.append(dir_name)
2514 return dir_name
2515
2516
Doug Zongkereef39442009-04-02 12:14:19 -07002517def Cleanup():
2518 for i in OPTIONS.tempfiles:
2519 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002520 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002521 else:
2522 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002523 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002524
2525
2526class PasswordManager(object):
2527 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002528 self.editor = os.getenv("EDITOR")
2529 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002530
2531 def GetPasswords(self, items):
2532 """Get passwords corresponding to each string in 'items',
2533 returning a dict. (The dict may have keys in addition to the
2534 values in 'items'.)
2535
2536 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2537 user edit that file to add more needed passwords. If no editor is
2538 available, or $ANDROID_PW_FILE isn't define, prompts the user
2539 interactively in the ordinary way.
2540 """
2541
2542 current = self.ReadFile()
2543
2544 first = True
2545 while True:
2546 missing = []
2547 for i in items:
2548 if i not in current or not current[i]:
2549 missing.append(i)
2550 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002551 if not missing:
2552 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002553
2554 for i in missing:
2555 current[i] = ""
2556
2557 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002558 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002559 if sys.version_info[0] >= 3:
2560 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002561 answer = raw_input("try to edit again? [y]> ").strip()
2562 if answer and answer[0] not in 'yY':
2563 raise RuntimeError("key passwords unavailable")
2564 first = False
2565
2566 current = self.UpdateAndReadFile(current)
2567
Kelvin Zhang0876c412020-06-23 15:06:58 -04002568 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002569 """Prompt the user to enter a value (password) for each key in
2570 'current' whose value is fales. Returns a new dict with all the
2571 values.
2572 """
2573 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002574 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002575 if v:
2576 result[k] = v
2577 else:
2578 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002579 result[k] = getpass.getpass(
2580 "Enter password for %s key> " % k).strip()
2581 if result[k]:
2582 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002583 return result
2584
2585 def UpdateAndReadFile(self, current):
2586 if not self.editor or not self.pwfile:
2587 return self.PromptResult(current)
2588
2589 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002590 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002591 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2592 f.write("# (Additional spaces are harmless.)\n\n")
2593
2594 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002595 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002596 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002597 f.write("[[[ %s ]]] %s\n" % (v, k))
2598 if not v and first_line is None:
2599 # position cursor on first line with no password.
2600 first_line = i + 4
2601 f.close()
2602
Tao Bao986ee862018-10-04 15:46:16 -07002603 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002604
2605 return self.ReadFile()
2606
2607 def ReadFile(self):
2608 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002609 if self.pwfile is None:
2610 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002611 try:
2612 f = open(self.pwfile, "r")
2613 for line in f:
2614 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002615 if not line or line[0] == '#':
2616 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002617 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2618 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002619 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002620 else:
2621 result[m.group(2)] = m.group(1)
2622 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002623 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002624 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002625 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002626 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002627
2628
Dan Albert8e0178d2015-01-27 15:53:15 -08002629def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2630 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002631
2632 # http://b/18015246
2633 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2634 # for files larger than 2GiB. We can work around this by adjusting their
2635 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2636 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2637 # it isn't clear to me exactly what circumstances cause this).
2638 # `zipfile.write()` must be used directly to work around this.
2639 #
2640 # This mess can be avoided if we port to python3.
2641 saved_zip64_limit = zipfile.ZIP64_LIMIT
2642 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2643
2644 if compress_type is None:
2645 compress_type = zip_file.compression
2646 if arcname is None:
2647 arcname = filename
2648
2649 saved_stat = os.stat(filename)
2650
2651 try:
2652 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2653 # file to be zipped and reset it when we're done.
2654 os.chmod(filename, perms)
2655
2656 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002657 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2658 # intentional. zip stores datetimes in local time without a time zone
2659 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2660 # in the zip archive.
2661 local_epoch = datetime.datetime.fromtimestamp(0)
2662 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002663 os.utime(filename, (timestamp, timestamp))
2664
2665 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2666 finally:
2667 os.chmod(filename, saved_stat.st_mode)
2668 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2669 zipfile.ZIP64_LIMIT = saved_zip64_limit
2670
2671
Tao Bao58c1b962015-05-20 09:32:18 -07002672def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002673 compress_type=None):
2674 """Wrap zipfile.writestr() function to work around the zip64 limit.
2675
2676 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2677 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2678 when calling crc32(bytes).
2679
2680 But it still works fine to write a shorter string into a large zip file.
2681 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2682 when we know the string won't be too long.
2683 """
2684
2685 saved_zip64_limit = zipfile.ZIP64_LIMIT
2686 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2687
2688 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2689 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002690 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002691 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002692 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002693 else:
Tao Baof3282b42015-04-01 11:21:55 -07002694 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002695 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2696 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2697 # such a case (since
2698 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2699 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2700 # permission bits. We follow the logic in Python 3 to get consistent
2701 # behavior between using the two versions.
2702 if not zinfo.external_attr:
2703 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002704
2705 # If compress_type is given, it overrides the value in zinfo.
2706 if compress_type is not None:
2707 zinfo.compress_type = compress_type
2708
Tao Bao58c1b962015-05-20 09:32:18 -07002709 # If perms is given, it has a priority.
2710 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002711 # If perms doesn't set the file type, mark it as a regular file.
2712 if perms & 0o770000 == 0:
2713 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002714 zinfo.external_attr = perms << 16
2715
Tao Baof3282b42015-04-01 11:21:55 -07002716 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002717 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2718
Dan Albert8b72aef2015-03-23 19:13:21 -07002719 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002720 zipfile.ZIP64_LIMIT = saved_zip64_limit
2721
2722
Tao Bao89d7ab22017-12-14 17:05:33 -08002723def ZipDelete(zip_filename, entries):
2724 """Deletes entries from a ZIP file.
2725
2726 Since deleting entries from a ZIP file is not supported, it shells out to
2727 'zip -d'.
2728
2729 Args:
2730 zip_filename: The name of the ZIP file.
2731 entries: The name of the entry, or the list of names to be deleted.
2732
2733 Raises:
2734 AssertionError: In case of non-zero return from 'zip'.
2735 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002736 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002737 entries = [entries]
2738 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002739 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002740
2741
Tao Baof3282b42015-04-01 11:21:55 -07002742def ZipClose(zip_file):
2743 # http://b/18015246
2744 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2745 # central directory.
2746 saved_zip64_limit = zipfile.ZIP64_LIMIT
2747 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2748
2749 zip_file.close()
2750
2751 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002752
2753
2754class DeviceSpecificParams(object):
2755 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002756
Doug Zongker05d3dea2009-06-22 11:32:31 -07002757 def __init__(self, **kwargs):
2758 """Keyword arguments to the constructor become attributes of this
2759 object, which is passed to all functions in the device-specific
2760 module."""
Tao Bao38884282019-07-10 22:20:56 -07002761 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002762 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002763 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002764
2765 if self.module is None:
2766 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002767 if not path:
2768 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002769 try:
2770 if os.path.isdir(path):
2771 info = imp.find_module("releasetools", [path])
2772 else:
2773 d, f = os.path.split(path)
2774 b, x = os.path.splitext(f)
2775 if x == ".py":
2776 f = b
2777 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002778 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002779 self.module = imp.load_module("device_specific", *info)
2780 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002781 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002782
2783 def _DoCall(self, function_name, *args, **kwargs):
2784 """Call the named function in the device-specific module, passing
2785 the given args and kwargs. The first argument to the call will be
2786 the DeviceSpecific object itself. If there is no module, or the
2787 module does not define the function, return the value of the
2788 'default' kwarg (which itself defaults to None)."""
2789 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002790 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002791 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2792
2793 def FullOTA_Assertions(self):
2794 """Called after emitting the block of assertions at the top of a
2795 full OTA package. Implementations can add whatever additional
2796 assertions they like."""
2797 return self._DoCall("FullOTA_Assertions")
2798
Doug Zongkere5ff5902012-01-17 10:55:37 -08002799 def FullOTA_InstallBegin(self):
2800 """Called at the start of full OTA installation."""
2801 return self._DoCall("FullOTA_InstallBegin")
2802
Yifan Hong10c530d2018-12-27 17:34:18 -08002803 def FullOTA_GetBlockDifferences(self):
2804 """Called during full OTA installation and verification.
2805 Implementation should return a list of BlockDifference objects describing
2806 the update on each additional partitions.
2807 """
2808 return self._DoCall("FullOTA_GetBlockDifferences")
2809
Doug Zongker05d3dea2009-06-22 11:32:31 -07002810 def FullOTA_InstallEnd(self):
2811 """Called at the end of full OTA installation; typically this is
2812 used to install the image for the device's baseband processor."""
2813 return self._DoCall("FullOTA_InstallEnd")
2814
2815 def IncrementalOTA_Assertions(self):
2816 """Called after emitting the block of assertions at the top of an
2817 incremental OTA package. Implementations can add whatever
2818 additional assertions they like."""
2819 return self._DoCall("IncrementalOTA_Assertions")
2820
Doug Zongkere5ff5902012-01-17 10:55:37 -08002821 def IncrementalOTA_VerifyBegin(self):
2822 """Called at the start of the verification phase of incremental
2823 OTA installation; additional checks can be placed here to abort
2824 the script before any changes are made."""
2825 return self._DoCall("IncrementalOTA_VerifyBegin")
2826
Doug Zongker05d3dea2009-06-22 11:32:31 -07002827 def IncrementalOTA_VerifyEnd(self):
2828 """Called at the end of the verification phase of incremental OTA
2829 installation; additional checks can be placed here to abort the
2830 script before any changes are made."""
2831 return self._DoCall("IncrementalOTA_VerifyEnd")
2832
Doug Zongkere5ff5902012-01-17 10:55:37 -08002833 def IncrementalOTA_InstallBegin(self):
2834 """Called at the start of incremental OTA installation (after
2835 verification is complete)."""
2836 return self._DoCall("IncrementalOTA_InstallBegin")
2837
Yifan Hong10c530d2018-12-27 17:34:18 -08002838 def IncrementalOTA_GetBlockDifferences(self):
2839 """Called during incremental OTA installation and verification.
2840 Implementation should return a list of BlockDifference objects describing
2841 the update on each additional partitions.
2842 """
2843 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2844
Doug Zongker05d3dea2009-06-22 11:32:31 -07002845 def IncrementalOTA_InstallEnd(self):
2846 """Called at the end of incremental OTA installation; typically
2847 this is used to install the image for the device's baseband
2848 processor."""
2849 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002850
Tao Bao9bc6bb22015-11-09 16:58:28 -08002851 def VerifyOTA_Assertions(self):
2852 return self._DoCall("VerifyOTA_Assertions")
2853
Tao Bao76def242017-11-21 09:25:31 -08002854
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002855class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002856 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002857 self.name = name
2858 self.data = data
2859 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002860 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002861 self.sha1 = sha1(data).hexdigest()
2862
2863 @classmethod
2864 def FromLocalFile(cls, name, diskname):
2865 f = open(diskname, "rb")
2866 data = f.read()
2867 f.close()
2868 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002869
2870 def WriteToTemp(self):
2871 t = tempfile.NamedTemporaryFile()
2872 t.write(self.data)
2873 t.flush()
2874 return t
2875
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002876 def WriteToDir(self, d):
2877 with open(os.path.join(d, self.name), "wb") as fp:
2878 fp.write(self.data)
2879
Geremy Condra36bd3652014-02-06 19:45:10 -08002880 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002881 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002882
Tao Bao76def242017-11-21 09:25:31 -08002883
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002884DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002885 ".gz": "imgdiff",
2886 ".zip": ["imgdiff", "-z"],
2887 ".jar": ["imgdiff", "-z"],
2888 ".apk": ["imgdiff", "-z"],
2889 ".img": "imgdiff",
2890}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002891
Tao Bao76def242017-11-21 09:25:31 -08002892
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002893class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002894 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002895 self.tf = tf
2896 self.sf = sf
2897 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002898 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002899
2900 def ComputePatch(self):
2901 """Compute the patch (as a string of data) needed to turn sf into
2902 tf. Returns the same tuple as GetPatch()."""
2903
2904 tf = self.tf
2905 sf = self.sf
2906
Doug Zongker24cd2802012-08-14 16:36:15 -07002907 if self.diff_program:
2908 diff_program = self.diff_program
2909 else:
2910 ext = os.path.splitext(tf.name)[1]
2911 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002912
2913 ttemp = tf.WriteToTemp()
2914 stemp = sf.WriteToTemp()
2915
2916 ext = os.path.splitext(tf.name)[1]
2917
2918 try:
2919 ptemp = tempfile.NamedTemporaryFile()
2920 if isinstance(diff_program, list):
2921 cmd = copy.copy(diff_program)
2922 else:
2923 cmd = [diff_program]
2924 cmd.append(stemp.name)
2925 cmd.append(ttemp.name)
2926 cmd.append(ptemp.name)
2927 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002928 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002929
Doug Zongkerf8340082014-08-05 10:39:37 -07002930 def run():
2931 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002932 if e:
2933 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002934 th = threading.Thread(target=run)
2935 th.start()
2936 th.join(timeout=300) # 5 mins
2937 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002938 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002939 p.terminate()
2940 th.join(5)
2941 if th.is_alive():
2942 p.kill()
2943 th.join()
2944
Tianjie Xua2a9f992018-01-05 15:15:54 -08002945 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002946 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002947 self.patch = None
2948 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002949 diff = ptemp.read()
2950 finally:
2951 ptemp.close()
2952 stemp.close()
2953 ttemp.close()
2954
2955 self.patch = diff
2956 return self.tf, self.sf, self.patch
2957
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002958 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002959 """Returns a tuple of (target_file, source_file, patch_data).
2960
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002961 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002962 computing the patch failed.
2963 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002964 return self.tf, self.sf, self.patch
2965
2966
2967def ComputeDifferences(diffs):
2968 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002969 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002970
2971 # Do the largest files first, to try and reduce the long-pole effect.
2972 by_size = [(i.tf.size, i) for i in diffs]
2973 by_size.sort(reverse=True)
2974 by_size = [i[1] for i in by_size]
2975
2976 lock = threading.Lock()
2977 diff_iter = iter(by_size) # accessed under lock
2978
2979 def worker():
2980 try:
2981 lock.acquire()
2982 for d in diff_iter:
2983 lock.release()
2984 start = time.time()
2985 d.ComputePatch()
2986 dur = time.time() - start
2987 lock.acquire()
2988
2989 tf, sf, patch = d.GetPatch()
2990 if sf.name == tf.name:
2991 name = tf.name
2992 else:
2993 name = "%s (%s)" % (tf.name, sf.name)
2994 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002995 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002996 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002997 logger.info(
2998 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2999 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003000 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003001 except Exception:
3002 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003003 raise
3004
3005 # start worker threads; wait for them all to finish.
3006 threads = [threading.Thread(target=worker)
3007 for i in range(OPTIONS.worker_threads)]
3008 for th in threads:
3009 th.start()
3010 while threads:
3011 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003012
3013
Dan Albert8b72aef2015-03-23 19:13:21 -07003014class BlockDifference(object):
3015 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003016 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003017 self.tgt = tgt
3018 self.src = src
3019 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003020 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003021 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003022
Tao Baodd2a5892015-03-12 12:32:37 -07003023 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003024 version = max(
3025 int(i) for i in
3026 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003027 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003028 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003029
Tianjie Xu41976c72019-07-03 13:57:01 -07003030 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3031 version=self.version,
3032 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003033 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003034 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003035 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003036 self.touched_src_ranges = b.touched_src_ranges
3037 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003038
Yifan Hong10c530d2018-12-27 17:34:18 -08003039 # On devices with dynamic partitions, for new partitions,
3040 # src is None but OPTIONS.source_info_dict is not.
3041 if OPTIONS.source_info_dict is None:
3042 is_dynamic_build = OPTIONS.info_dict.get(
3043 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003044 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003045 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003046 is_dynamic_build = OPTIONS.source_info_dict.get(
3047 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003048 is_dynamic_source = partition in shlex.split(
3049 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003050
Yifan Hongbb2658d2019-01-25 12:30:58 -08003051 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003052 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3053
Yifan Hongbb2658d2019-01-25 12:30:58 -08003054 # For dynamic partitions builds, check partition list in both source
3055 # and target build because new partitions may be added, and existing
3056 # partitions may be removed.
3057 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3058
Yifan Hong10c530d2018-12-27 17:34:18 -08003059 if is_dynamic:
3060 self.device = 'map_partition("%s")' % partition
3061 else:
3062 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003063 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3064 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003065 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003066 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3067 OPTIONS.source_info_dict)
3068 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003069
Tao Baod8d14be2016-02-04 14:26:02 -08003070 @property
3071 def required_cache(self):
3072 return self._required_cache
3073
Tao Bao76def242017-11-21 09:25:31 -08003074 def WriteScript(self, script, output_zip, progress=None,
3075 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003076 if not self.src:
3077 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003078 script.Print("Patching %s image unconditionally..." % (self.partition,))
3079 else:
3080 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003081
Dan Albert8b72aef2015-03-23 19:13:21 -07003082 if progress:
3083 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003084 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003085
3086 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003087 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003088
Tao Bao9bc6bb22015-11-09 16:58:28 -08003089 def WriteStrictVerifyScript(self, script):
3090 """Verify all the blocks in the care_map, including clobbered blocks.
3091
3092 This differs from the WriteVerifyScript() function: a) it prints different
3093 error messages; b) it doesn't allow half-way updated images to pass the
3094 verification."""
3095
3096 partition = self.partition
3097 script.Print("Verifying %s..." % (partition,))
3098 ranges = self.tgt.care_map
3099 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003100 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003101 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3102 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003103 self.device, ranges_str,
3104 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003105 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003106 script.AppendExtra("")
3107
Tao Baod522bdc2016-04-12 15:53:16 -07003108 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003109 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003110
3111 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003112 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003113 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003114
3115 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003116 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003117 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003118 ranges = self.touched_src_ranges
3119 expected_sha1 = self.touched_src_sha1
3120 else:
3121 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3122 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003123
3124 # No blocks to be checked, skipping.
3125 if not ranges:
3126 return
3127
Tao Bao5ece99d2015-05-12 11:42:31 -07003128 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003129 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003130 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003131 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3132 '"%s.patch.dat")) then' % (
3133 self.device, ranges_str, expected_sha1,
3134 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003135 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003136 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003137
Tianjie Xufc3422a2015-12-15 11:53:59 -08003138 if self.version >= 4:
3139
3140 # Bug: 21124327
3141 # When generating incrementals for the system and vendor partitions in
3142 # version 4 or newer, explicitly check the first block (which contains
3143 # the superblock) of the partition to see if it's what we expect. If
3144 # this check fails, give an explicit log message about the partition
3145 # having been remounted R/W (the most likely explanation).
3146 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003147 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003148
3149 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003150 if partition == "system":
3151 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3152 else:
3153 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003154 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003155 'ifelse (block_image_recover({device}, "{ranges}") && '
3156 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003157 'package_extract_file("{partition}.transfer.list"), '
3158 '"{partition}.new.dat", "{partition}.patch.dat"), '
3159 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003160 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003161 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003162 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003163
Tao Baodd2a5892015-03-12 12:32:37 -07003164 # Abort the OTA update. Note that the incremental OTA cannot be applied
3165 # even if it may match the checksum of the target partition.
3166 # a) If version < 3, operations like move and erase will make changes
3167 # unconditionally and damage the partition.
3168 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003169 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003170 if partition == "system":
3171 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3172 else:
3173 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3174 script.AppendExtra((
3175 'abort("E%d: %s partition has unexpected contents");\n'
3176 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003177
Yifan Hong10c530d2018-12-27 17:34:18 -08003178 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003179 partition = self.partition
3180 script.Print('Verifying the updated %s image...' % (partition,))
3181 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3182 ranges = self.tgt.care_map
3183 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003184 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003185 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003186 self.device, ranges_str,
3187 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003188
3189 # Bug: 20881595
3190 # Verify that extended blocks are really zeroed out.
3191 if self.tgt.extended:
3192 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003193 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003194 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003195 self.device, ranges_str,
3196 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003197 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003198 if partition == "system":
3199 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3200 else:
3201 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003202 script.AppendExtra(
3203 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003204 ' abort("E%d: %s partition has unexpected non-zero contents after '
3205 'OTA update");\n'
3206 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003207 else:
3208 script.Print('Verified the updated %s image.' % (partition,))
3209
Tianjie Xu209db462016-05-24 17:34:52 -07003210 if partition == "system":
3211 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3212 else:
3213 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3214
Tao Bao5fcaaef2015-06-01 13:40:49 -07003215 script.AppendExtra(
3216 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003217 ' abort("E%d: %s partition has unexpected contents after OTA '
3218 'update");\n'
3219 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003220
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003221 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003222 ZipWrite(output_zip,
3223 '{}.transfer.list'.format(self.path),
3224 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003225
Tao Bao76def242017-11-21 09:25:31 -08003226 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3227 # its size. Quailty 9 almost triples the compression time but doesn't
3228 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003229 # zip | brotli(quality 6) | brotli(quality 9)
3230 # compressed_size: 942M | 869M (~8% reduced) | 854M
3231 # compression_time: 75s | 265s | 719s
3232 # decompression_time: 15s | 25s | 25s
3233
3234 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003235 brotli_cmd = ['brotli', '--quality=6',
3236 '--output={}.new.dat.br'.format(self.path),
3237 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003238 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003239 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003240
3241 new_data_name = '{}.new.dat.br'.format(self.partition)
3242 ZipWrite(output_zip,
3243 '{}.new.dat.br'.format(self.path),
3244 new_data_name,
3245 compress_type=zipfile.ZIP_STORED)
3246 else:
3247 new_data_name = '{}.new.dat'.format(self.partition)
3248 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3249
Dan Albert8e0178d2015-01-27 15:53:15 -08003250 ZipWrite(output_zip,
3251 '{}.patch.dat'.format(self.path),
3252 '{}.patch.dat'.format(self.partition),
3253 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003254
Tianjie Xu209db462016-05-24 17:34:52 -07003255 if self.partition == "system":
3256 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3257 else:
3258 code = ErrorCode.VENDOR_UPDATE_FAILURE
3259
Yifan Hong10c530d2018-12-27 17:34:18 -08003260 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003261 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003262 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003263 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003264 device=self.device, partition=self.partition,
3265 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003266 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003267
Kelvin Zhang0876c412020-06-23 15:06:58 -04003268 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003269 data = source.ReadRangeSet(ranges)
3270 ctx = sha1()
3271
3272 for p in data:
3273 ctx.update(p)
3274
3275 return ctx.hexdigest()
3276
Kelvin Zhang0876c412020-06-23 15:06:58 -04003277 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003278 """Return the hash value for all zero blocks."""
3279 zero_block = '\x00' * 4096
3280 ctx = sha1()
3281 for _ in range(num_blocks):
3282 ctx.update(zero_block)
3283
3284 return ctx.hexdigest()
3285
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003286
Tianjie Xu41976c72019-07-03 13:57:01 -07003287# Expose these two classes to support vendor-specific scripts
3288DataImage = images.DataImage
3289EmptyImage = images.EmptyImage
3290
Tao Bao76def242017-11-21 09:25:31 -08003291
Doug Zongker96a57e72010-09-26 14:57:41 -07003292# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003293PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003294 "ext4": "EMMC",
3295 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003296 "f2fs": "EMMC",
3297 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003298}
Doug Zongker96a57e72010-09-26 14:57:41 -07003299
Kelvin Zhang0876c412020-06-23 15:06:58 -04003300
Yifan Hongbdb32012020-05-07 12:38:53 -07003301def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3302 """
3303 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3304 backwards compatibility. It aborts if the fstab entry has slotselect option
3305 (unless check_no_slot is explicitly set to False).
3306 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003307 fstab = info["fstab"]
3308 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003309 if check_no_slot:
3310 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003311 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003312 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3313 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003314 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003315
3316
Yifan Hongbdb32012020-05-07 12:38:53 -07003317def GetTypeAndDeviceExpr(mount_point, info):
3318 """
3319 Return the filesystem of the partition, and an edify expression that evaluates
3320 to the device at runtime.
3321 """
3322 fstab = info["fstab"]
3323 if fstab:
3324 p = fstab[mount_point]
3325 device_expr = '"%s"' % fstab[mount_point].device
3326 if p.slotselect:
3327 device_expr = 'add_slot_suffix(%s)' % device_expr
3328 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003329 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003330
3331
3332def GetEntryForDevice(fstab, device):
3333 """
3334 Returns:
3335 The first entry in fstab whose device is the given value.
3336 """
3337 if not fstab:
3338 return None
3339 for mount_point in fstab:
3340 if fstab[mount_point].device == device:
3341 return fstab[mount_point]
3342 return None
3343
Kelvin Zhang0876c412020-06-23 15:06:58 -04003344
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003345def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003346 """Parses and converts a PEM-encoded certificate into DER-encoded.
3347
3348 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3349
3350 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003351 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003352 """
3353 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003354 save = False
3355 for line in data.split("\n"):
3356 if "--END CERTIFICATE--" in line:
3357 break
3358 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003359 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003360 if "--BEGIN CERTIFICATE--" in line:
3361 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003362 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003363 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003364
Tao Bao04e1f012018-02-04 12:13:35 -08003365
3366def ExtractPublicKey(cert):
3367 """Extracts the public key (PEM-encoded) from the given certificate file.
3368
3369 Args:
3370 cert: The certificate filename.
3371
3372 Returns:
3373 The public key string.
3374
3375 Raises:
3376 AssertionError: On non-zero return from 'openssl'.
3377 """
3378 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3379 # While openssl 1.1 writes the key into the given filename followed by '-out',
3380 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3381 # stdout instead.
3382 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3383 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3384 pubkey, stderrdata = proc.communicate()
3385 assert proc.returncode == 0, \
3386 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3387 return pubkey
3388
3389
Tao Bao1ac886e2019-06-26 11:58:22 -07003390def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003391 """Extracts the AVB public key from the given public or private key.
3392
3393 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003394 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003395 key: The input key file, which should be PEM-encoded public or private key.
3396
3397 Returns:
3398 The path to the extracted AVB public key file.
3399 """
3400 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3401 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003402 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003403 return output
3404
3405
Doug Zongker412c02f2014-02-13 10:58:24 -08003406def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3407 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003408 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003409
Tao Bao6d5d6232018-03-09 17:04:42 -08003410 Most of the space in the boot and recovery images is just the kernel, which is
3411 identical for the two, so the resulting patch should be efficient. Add it to
3412 the output zip, along with a shell script that is run from init.rc on first
3413 boot to actually do the patching and install the new recovery image.
3414
3415 Args:
3416 input_dir: The top-level input directory of the target-files.zip.
3417 output_sink: The callback function that writes the result.
3418 recovery_img: File object for the recovery image.
3419 boot_img: File objects for the boot image.
3420 info_dict: A dict returned by common.LoadInfoDict() on the input
3421 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003422 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003423 if info_dict is None:
3424 info_dict = OPTIONS.info_dict
3425
Tao Bao6d5d6232018-03-09 17:04:42 -08003426 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003427 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3428
3429 if board_uses_vendorimage:
3430 # In this case, the output sink is rooted at VENDOR
3431 recovery_img_path = "etc/recovery.img"
3432 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3433 sh_dir = "bin"
3434 else:
3435 # In this case the output sink is rooted at SYSTEM
3436 recovery_img_path = "vendor/etc/recovery.img"
3437 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3438 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003439
Tao Baof2cffbd2015-07-22 12:33:18 -07003440 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003441 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003442
3443 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003444 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003445 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003446 # With system-root-image, boot and recovery images will have mismatching
3447 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3448 # to handle such a case.
3449 if system_root_image:
3450 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003451 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003452 assert not os.path.exists(path)
3453 else:
3454 diff_program = ["imgdiff"]
3455 if os.path.exists(path):
3456 diff_program.append("-b")
3457 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003458 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003459 else:
3460 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003461
3462 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3463 _, _, patch = d.ComputePatch()
3464 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003465
Dan Albertebb19aa2015-03-27 19:11:53 -07003466 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003467 # The following GetTypeAndDevice()s need to use the path in the target
3468 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003469 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3470 check_no_slot=False)
3471 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3472 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003473 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003474 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003475
Tao Baof2cffbd2015-07-22 12:33:18 -07003476 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003477
3478 # Note that we use /vendor to refer to the recovery resources. This will
3479 # work for a separate vendor partition mounted at /vendor or a
3480 # /system/vendor subdirectory on the system partition, for which init will
3481 # create a symlink from /vendor to /system/vendor.
3482
3483 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003484if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3485 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003486 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003487 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3488 log -t recovery "Installing new recovery image: succeeded" || \\
3489 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003490else
3491 log -t recovery "Recovery image already installed"
3492fi
3493""" % {'type': recovery_type,
3494 'device': recovery_device,
3495 'sha1': recovery_img.sha1,
3496 'size': recovery_img.size}
3497 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003498 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003499if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3500 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003501 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003502 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3503 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3504 log -t recovery "Installing new recovery image: succeeded" || \\
3505 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003506else
3507 log -t recovery "Recovery image already installed"
3508fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003509""" % {'boot_size': boot_img.size,
3510 'boot_sha1': boot_img.sha1,
3511 'recovery_size': recovery_img.size,
3512 'recovery_sha1': recovery_img.sha1,
3513 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003514 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003515 'recovery_type': recovery_type,
3516 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003517 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003518
Bill Peckhame868aec2019-09-17 17:06:47 -07003519 # The install script location moved from /system/etc to /system/bin in the L
3520 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3521 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003522
Tao Bao32fcdab2018-10-12 10:30:39 -07003523 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003524
Tao Baoda30cfa2017-12-01 16:19:46 -08003525 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003526
3527
3528class DynamicPartitionUpdate(object):
3529 def __init__(self, src_group=None, tgt_group=None, progress=None,
3530 block_difference=None):
3531 self.src_group = src_group
3532 self.tgt_group = tgt_group
3533 self.progress = progress
3534 self.block_difference = block_difference
3535
3536 @property
3537 def src_size(self):
3538 if not self.block_difference:
3539 return 0
3540 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3541
3542 @property
3543 def tgt_size(self):
3544 if not self.block_difference:
3545 return 0
3546 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3547
3548 @staticmethod
3549 def _GetSparseImageSize(img):
3550 if not img:
3551 return 0
3552 return img.blocksize * img.total_blocks
3553
3554
3555class DynamicGroupUpdate(object):
3556 def __init__(self, src_size=None, tgt_size=None):
3557 # None: group does not exist. 0: no size limits.
3558 self.src_size = src_size
3559 self.tgt_size = tgt_size
3560
3561
3562class DynamicPartitionsDifference(object):
3563 def __init__(self, info_dict, block_diffs, progress_dict=None,
3564 source_info_dict=None):
3565 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003566 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003567
3568 self._remove_all_before_apply = False
3569 if source_info_dict is None:
3570 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003571 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003572
Tao Baof1113e92019-06-18 12:10:14 -07003573 block_diff_dict = collections.OrderedDict(
3574 [(e.partition, e) for e in block_diffs])
3575
Yifan Hong10c530d2018-12-27 17:34:18 -08003576 assert len(block_diff_dict) == len(block_diffs), \
3577 "Duplicated BlockDifference object for {}".format(
3578 [partition for partition, count in
3579 collections.Counter(e.partition for e in block_diffs).items()
3580 if count > 1])
3581
Yifan Hong79997e52019-01-23 16:56:19 -08003582 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003583
3584 for p, block_diff in block_diff_dict.items():
3585 self._partition_updates[p] = DynamicPartitionUpdate()
3586 self._partition_updates[p].block_difference = block_diff
3587
3588 for p, progress in progress_dict.items():
3589 if p in self._partition_updates:
3590 self._partition_updates[p].progress = progress
3591
3592 tgt_groups = shlex.split(info_dict.get(
3593 "super_partition_groups", "").strip())
3594 src_groups = shlex.split(source_info_dict.get(
3595 "super_partition_groups", "").strip())
3596
3597 for g in tgt_groups:
3598 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003599 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003600 assert p in self._partition_updates, \
3601 "{} is in target super_{}_partition_list but no BlockDifference " \
3602 "object is provided.".format(p, g)
3603 self._partition_updates[p].tgt_group = g
3604
3605 for g in src_groups:
3606 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003607 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003608 assert p in self._partition_updates, \
3609 "{} is in source super_{}_partition_list but no BlockDifference " \
3610 "object is provided.".format(p, g)
3611 self._partition_updates[p].src_group = g
3612
Yifan Hong45433e42019-01-18 13:55:25 -08003613 target_dynamic_partitions = set(shlex.split(info_dict.get(
3614 "dynamic_partition_list", "").strip()))
3615 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3616 if u.tgt_size)
3617 assert block_diffs_with_target == target_dynamic_partitions, \
3618 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3619 list(target_dynamic_partitions), list(block_diffs_with_target))
3620
3621 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3622 "dynamic_partition_list", "").strip()))
3623 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3624 if u.src_size)
3625 assert block_diffs_with_source == source_dynamic_partitions, \
3626 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3627 list(source_dynamic_partitions), list(block_diffs_with_source))
3628
Yifan Hong10c530d2018-12-27 17:34:18 -08003629 if self._partition_updates:
3630 logger.info("Updating dynamic partitions %s",
3631 self._partition_updates.keys())
3632
Yifan Hong79997e52019-01-23 16:56:19 -08003633 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003634
3635 for g in tgt_groups:
3636 self._group_updates[g] = DynamicGroupUpdate()
3637 self._group_updates[g].tgt_size = int(info_dict.get(
3638 "super_%s_group_size" % g, "0").strip())
3639
3640 for g in src_groups:
3641 if g not in self._group_updates:
3642 self._group_updates[g] = DynamicGroupUpdate()
3643 self._group_updates[g].src_size = int(source_info_dict.get(
3644 "super_%s_group_size" % g, "0").strip())
3645
3646 self._Compute()
3647
3648 def WriteScript(self, script, output_zip, write_verify_script=False):
3649 script.Comment('--- Start patching dynamic partitions ---')
3650 for p, u in self._partition_updates.items():
3651 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3652 script.Comment('Patch partition %s' % p)
3653 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3654 write_verify_script=False)
3655
3656 op_list_path = MakeTempFile()
3657 with open(op_list_path, 'w') as f:
3658 for line in self._op_list:
3659 f.write('{}\n'.format(line))
3660
3661 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3662
3663 script.Comment('Update dynamic partition metadata')
3664 script.AppendExtra('assert(update_dynamic_partitions('
3665 'package_extract_file("dynamic_partitions_op_list")));')
3666
3667 if write_verify_script:
3668 for p, u in self._partition_updates.items():
3669 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3670 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003671 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003672
3673 for p, u in self._partition_updates.items():
3674 if u.tgt_size and u.src_size <= u.tgt_size:
3675 script.Comment('Patch partition %s' % p)
3676 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3677 write_verify_script=write_verify_script)
3678 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003679 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003680
3681 script.Comment('--- End patching dynamic partitions ---')
3682
3683 def _Compute(self):
3684 self._op_list = list()
3685
3686 def append(line):
3687 self._op_list.append(line)
3688
3689 def comment(line):
3690 self._op_list.append("# %s" % line)
3691
3692 if self._remove_all_before_apply:
3693 comment('Remove all existing dynamic partitions and groups before '
3694 'applying full OTA')
3695 append('remove_all_groups')
3696
3697 for p, u in self._partition_updates.items():
3698 if u.src_group and not u.tgt_group:
3699 append('remove %s' % p)
3700
3701 for p, u in self._partition_updates.items():
3702 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3703 comment('Move partition %s from %s to default' % (p, u.src_group))
3704 append('move %s default' % p)
3705
3706 for p, u in self._partition_updates.items():
3707 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3708 comment('Shrink partition %s from %d to %d' %
3709 (p, u.src_size, u.tgt_size))
3710 append('resize %s %s' % (p, u.tgt_size))
3711
3712 for g, u in self._group_updates.items():
3713 if u.src_size is not None and u.tgt_size is None:
3714 append('remove_group %s' % g)
3715 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003716 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003717 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3718 append('resize_group %s %d' % (g, u.tgt_size))
3719
3720 for g, u in self._group_updates.items():
3721 if u.src_size is None and u.tgt_size is not None:
3722 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3723 append('add_group %s %d' % (g, u.tgt_size))
3724 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003725 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003726 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3727 append('resize_group %s %d' % (g, u.tgt_size))
3728
3729 for p, u in self._partition_updates.items():
3730 if u.tgt_group and not u.src_group:
3731 comment('Add partition %s to group %s' % (p, u.tgt_group))
3732 append('add %s %s' % (p, u.tgt_group))
3733
3734 for p, u in self._partition_updates.items():
3735 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003736 comment('Grow partition %s from %d to %d' %
3737 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003738 append('resize %s %d' % (p, u.tgt_size))
3739
3740 for p, u in self._partition_updates.items():
3741 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3742 comment('Move partition %s from default to %s' %
3743 (p, u.tgt_group))
3744 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003745
3746
jiajia tangf3f842b2021-03-17 21:49:44 +08003747def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003748 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003749 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003750
3751 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003752 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003753
3754 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003755 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003756 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003757 tmp_dir = MakeTempDir('boot_', suffix='.img')
3758 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003759 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3760 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003761 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3762 if not os.path.isfile(ramdisk):
3763 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3764 return None
3765 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003766 if ramdisk_format == RamdiskFormat.LZ4:
3767 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3768 elif ramdisk_format == RamdiskFormat.GZ:
3769 with open(ramdisk, 'rb') as input_stream:
3770 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003771 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3772 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003773 p2.wait()
3774 else:
3775 logger.error('Only support lz4 or minigzip ramdisk format.')
3776 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003777
3778 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3779 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3780 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3781 # the host environment.
3782 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003783 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003784
Yifan Hongc65a0542021-01-07 14:21:01 -08003785 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3786 prop_file = os.path.join(extracted_ramdisk, search_path)
3787 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003788 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003789 logger.warning(
3790 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003791
Yifan Hong7dc51172021-01-12 11:27:39 -08003792 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003793
Yifan Hong85ac5012021-01-07 14:43:46 -08003794 except ExternalError as e:
3795 logger.warning('Unable to get boot image build props: %s', e)
3796 return None
3797
3798
3799def GetBootImageTimestamp(boot_img):
3800 """
3801 Get timestamp from ramdisk within the boot image
3802
3803 Args:
3804 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3805
3806 Return:
3807 An integer that corresponds to the timestamp of the boot image, or None
3808 if file has unknown format. Raise exception if an unexpected error has
3809 occurred.
3810 """
3811 prop_file = GetBootImageBuildProp(boot_img)
3812 if not prop_file:
3813 return None
3814
3815 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3816 if props is None:
3817 return None
3818
3819 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003820 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3821 if timestamp:
3822 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003823 logger.warning(
3824 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003825 return None
3826
3827 except ExternalError as e:
3828 logger.warning('Unable to get boot image timestamp: %s', e)
3829 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003830
3831
3832def GetCareMap(which, imgname):
3833 """Returns the care_map string for the given partition.
3834
3835 Args:
3836 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3837 imgname: The filename of the image.
3838
3839 Returns:
3840 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3841 RangeSet; or None.
3842 """
3843 assert which in PARTITIONS_WITH_CARE_MAP
3844
3845 # which + "_image_size" contains the size that the actual filesystem image
3846 # resides in, which is all that needs to be verified. The additional blocks in
3847 # the image file contain verity metadata, by reading which would trigger
3848 # invalid reads.
3849 image_size = OPTIONS.info_dict.get(which + "_image_size")
3850 if not image_size:
3851 return None
3852
3853 image_blocks = int(image_size) // 4096 - 1
3854 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3855
3856 # For sparse images, we will only check the blocks that are listed in the care
3857 # map, i.e. the ones with meaningful data.
3858 if "extfs_sparse_flag" in OPTIONS.info_dict:
3859 simg = sparse_img.SparseImage(imgname)
3860 care_map_ranges = simg.care_map.intersect(
3861 rangelib.RangeSet("0-{}".format(image_blocks)))
3862
3863 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3864 # image.
3865 else:
3866 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3867
3868 return [which, care_map_ranges.to_string_raw()]
3869
3870
3871def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3872 """Generates and adds care_map.pb for a/b partition that has care_map.
3873
3874 Args:
3875 output_file: The output zip file (needs to be already open),
3876 or file path to write care_map.pb.
3877 ab_partitions: The list of A/B partitions.
3878 image_paths: A map from the partition name to the image path.
3879 """
3880 if not output_file:
3881 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3882
3883 care_map_list = []
3884 for partition in ab_partitions:
3885 partition = partition.strip()
3886 if partition not in PARTITIONS_WITH_CARE_MAP:
3887 continue
3888
3889 verity_block_device = "{}_verity_block_device".format(partition)
3890 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3891 if (verity_block_device in OPTIONS.info_dict or
3892 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3893 if partition not in image_paths:
3894 logger.warning('Potential partition with care_map missing from images: %s',
3895 partition)
3896 continue
3897 image_path = image_paths[partition]
3898 if not os.path.exists(image_path):
3899 raise ExternalError('Expected image at path {}'.format(image_path))
3900
3901 care_map = GetCareMap(partition, image_path)
3902 if not care_map:
3903 continue
3904 care_map_list += care_map
3905
3906 # adds fingerprint field to the care_map
3907 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3908 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3909 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3910 "ro.{}.build.thumbprint".format(partition)]
3911
3912 present_props = [x for x in prop_name_list if
3913 partition_props and partition_props.GetProp(x)]
3914 if not present_props:
3915 logger.warning(
3916 "fingerprint is not present for partition %s", partition)
3917 property_id, fingerprint = "unknown", "unknown"
3918 else:
3919 property_id = present_props[0]
3920 fingerprint = partition_props.GetProp(property_id)
3921 care_map_list += [property_id, fingerprint]
3922
3923 if not care_map_list:
3924 return
3925
3926 # Converts the list into proto buf message by calling care_map_generator; and
3927 # writes the result to a temp file.
3928 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3929 suffix=".txt")
3930 with open(temp_care_map_text, 'w') as text_file:
3931 text_file.write('\n'.join(care_map_list))
3932
3933 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3934 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3935 RunAndCheckOutput(care_map_gen_cmd)
3936
3937 if not isinstance(output_file, zipfile.ZipFile):
3938 shutil.copy(temp_care_map, output_file)
3939 return
3940 # output_file is a zip file
3941 care_map_path = "META/care_map.pb"
3942 if care_map_path in output_file.namelist():
3943 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3944 # replace_updated_files_list used by add_img_to_target_files
3945 if not OPTIONS.replace_updated_files_list:
3946 OPTIONS.replace_updated_files_list = []
3947 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3948 OPTIONS.replace_updated_files_list.append(care_map_path)
3949 else:
3950 ZipWrite(output_file, temp_care_map, arcname=care_map_path)