blob: 985a21ab776e70e9232b3f08f4191193af9c458e [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
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001713def _SignBootableImage(image_path, prebuilt_name, partition_name,
1714 info_dict=None):
1715 """Performs AVB signing for a prebuilt boot.img.
1716
1717 Args:
1718 image_path: The full path of the image, e.g., /path/to/boot.img.
1719 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1720 boot-5.10.img, recovery.img.
1721 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1722 info_dict: The information dict read from misc_info.txt.
1723 """
1724 if info_dict is None:
1725 info_dict = OPTIONS.info_dict
1726
1727 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1728 if info_dict.get("avb_enable") == "true":
1729 avbtool = info_dict["avb_avbtool"]
1730 if partition_name == "recovery":
1731 part_size = info_dict["recovery_size"]
1732 else:
1733 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1734
1735 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1736 "--partition_size", str(part_size), "--partition_name",
1737 partition_name]
1738 AppendAVBSigningArgs(cmd, partition_name)
1739 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1740 if args and args.strip():
1741 cmd.extend(shlex.split(args))
1742 RunAndCheckOutput(cmd)
1743
1744
Doug Zongkerd5131602012-08-02 14:46:42 -07001745def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001746 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001747 """Return a File object with the desired bootable image.
1748
1749 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1750 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1751 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001752
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001753 if info_dict is None:
1754 info_dict = OPTIONS.info_dict
1755
Doug Zongker55d93282011-01-25 17:03:34 -08001756 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1757 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001758 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001759 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001760
1761 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1762 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001763 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001764 return File.FromLocalFile(name, prebuilt_path)
1765
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001766 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1767 if os.path.exists(prebuilt_path):
1768 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1769 signed_img = MakeTempFile()
1770 shutil.copy(prebuilt_path, signed_img)
1771 partition_name = tree_subdir.lower()
1772 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1773 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001774
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001775 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001776
1777 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001778 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1779 # for recovery.
1780 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1781 prebuilt_name != "boot.img" or
1782 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001783
Doug Zongker6f1d0312014-08-22 08:07:12 -07001784 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001785 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001786 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001787 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001788 if data:
1789 return File(name, data)
1790 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001791
Doug Zongkereef39442009-04-02 12:14:19 -07001792
Steve Mucklee1b10862019-07-10 10:49:37 -07001793def _BuildVendorBootImage(sourcedir, info_dict=None):
1794 """Build a vendor boot image from the specified sourcedir.
1795
1796 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1797 turn them into a vendor boot image.
1798
1799 Return the image data, or None if sourcedir does not appear to contains files
1800 for building the requested image.
1801 """
1802
1803 if info_dict is None:
1804 info_dict = OPTIONS.info_dict
1805
1806 img = tempfile.NamedTemporaryFile()
1807
jiajia tang836f76b2021-04-02 14:48:26 +08001808 ramdisk_format = _GetRamdiskFormat(info_dict)
1809 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001810
1811 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1812 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1813
1814 cmd = [mkbootimg]
1815
1816 fn = os.path.join(sourcedir, "dtb")
1817 if os.access(fn, os.F_OK):
1818 cmd.append("--dtb")
1819 cmd.append(fn)
1820
1821 fn = os.path.join(sourcedir, "vendor_cmdline")
1822 if os.access(fn, os.F_OK):
1823 cmd.append("--vendor_cmdline")
1824 cmd.append(open(fn).read().rstrip("\n"))
1825
1826 fn = os.path.join(sourcedir, "base")
1827 if os.access(fn, os.F_OK):
1828 cmd.append("--base")
1829 cmd.append(open(fn).read().rstrip("\n"))
1830
1831 fn = os.path.join(sourcedir, "pagesize")
1832 if os.access(fn, os.F_OK):
1833 cmd.append("--pagesize")
1834 cmd.append(open(fn).read().rstrip("\n"))
1835
1836 args = info_dict.get("mkbootimg_args")
1837 if args and args.strip():
1838 cmd.extend(shlex.split(args))
1839
1840 args = info_dict.get("mkbootimg_version_args")
1841 if args and args.strip():
1842 cmd.extend(shlex.split(args))
1843
1844 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1845 cmd.extend(["--vendor_boot", img.name])
1846
Devin Moore50509012021-01-13 10:45:04 -08001847 fn = os.path.join(sourcedir, "vendor_bootconfig")
1848 if os.access(fn, os.F_OK):
1849 cmd.append("--vendor_bootconfig")
1850 cmd.append(fn)
1851
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001852 ramdisk_fragment_imgs = []
1853 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1854 if os.access(fn, os.F_OK):
1855 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1856 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001857 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1858 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001859 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001860 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1861 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001862 # Use prebuilt image if found, else create ramdisk from supplied files.
1863 if os.access(fn, os.F_OK):
1864 ramdisk_fragment_pathname = fn
1865 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001866 ramdisk_fragment_root = os.path.join(
1867 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001868 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1869 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001870 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1871 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1872 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1873
Steve Mucklee1b10862019-07-10 10:49:37 -07001874 RunAndCheckOutput(cmd)
1875
1876 # AVB: if enabled, calculate and add hash.
1877 if info_dict.get("avb_enable") == "true":
1878 avbtool = info_dict["avb_avbtool"]
1879 part_size = info_dict["vendor_boot_size"]
1880 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001881 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001882 AppendAVBSigningArgs(cmd, "vendor_boot")
1883 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1884 if args and args.strip():
1885 cmd.extend(shlex.split(args))
1886 RunAndCheckOutput(cmd)
1887
1888 img.seek(os.SEEK_SET, 0)
1889 data = img.read()
1890
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001891 for f in ramdisk_fragment_imgs:
1892 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001893 ramdisk_img.close()
1894 img.close()
1895
1896 return data
1897
1898
1899def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1900 info_dict=None):
1901 """Return a File object with the desired vendor boot image.
1902
1903 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1904 the source files in 'unpack_dir'/'tree_subdir'."""
1905
1906 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1907 if os.path.exists(prebuilt_path):
1908 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1909 return File.FromLocalFile(name, prebuilt_path)
1910
1911 logger.info("building image from target_files %s...", tree_subdir)
1912
1913 if info_dict is None:
1914 info_dict = OPTIONS.info_dict
1915
Kelvin Zhang0876c412020-06-23 15:06:58 -04001916 data = _BuildVendorBootImage(
1917 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001918 if data:
1919 return File(name, data)
1920 return None
1921
1922
Narayan Kamatha07bf042017-08-14 14:49:21 +01001923def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001924 """Gunzips the given gzip compressed file to a given output file."""
1925 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001926 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001927 shutil.copyfileobj(in_file, out_file)
1928
1929
Tao Bao0ff15de2019-03-20 11:26:06 -07001930def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001931 """Unzips the archive to the given directory.
1932
1933 Args:
1934 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001935 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001936 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1937 archvie. Non-matching patterns will be filtered out. If there's no match
1938 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001939 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001940 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001941 if patterns is not None:
1942 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001943 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001944 names = input_zip.namelist()
1945 filtered = [
1946 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1947
1948 # There isn't any matching files. Don't unzip anything.
1949 if not filtered:
1950 return
1951 cmd.extend(filtered)
1952
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001953 RunAndCheckOutput(cmd)
1954
1955
Doug Zongker75f17362009-12-08 13:46:44 -08001956def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001957 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001958
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001959 Args:
1960 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1961 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1962
1963 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1964 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001965
Tao Bao1c830bf2017-12-25 10:43:47 -08001966 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001967 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001968 """
Doug Zongkereef39442009-04-02 12:14:19 -07001969
Tao Bao1c830bf2017-12-25 10:43:47 -08001970 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001971 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1972 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001973 UnzipToDir(m.group(1), tmp, pattern)
1974 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001975 filename = m.group(1)
1976 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001977 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001978
Tao Baodba59ee2018-01-09 13:21:02 -08001979 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001980
1981
Yifan Hong8a66a712019-04-04 15:37:57 -07001982def GetUserImage(which, tmpdir, input_zip,
1983 info_dict=None,
1984 allow_shared_blocks=None,
1985 hashtree_info_generator=None,
1986 reset_file_map=False):
1987 """Returns an Image object suitable for passing to BlockImageDiff.
1988
1989 This function loads the specified image from the given path. If the specified
1990 image is sparse, it also performs additional processing for OTA purpose. For
1991 example, it always adds block 0 to clobbered blocks list. It also detects
1992 files that cannot be reconstructed from the block list, for whom we should
1993 avoid applying imgdiff.
1994
1995 Args:
1996 which: The partition name.
1997 tmpdir: The directory that contains the prebuilt image and block map file.
1998 input_zip: The target-files ZIP archive.
1999 info_dict: The dict to be looked up for relevant info.
2000 allow_shared_blocks: If image is sparse, whether having shared blocks is
2001 allowed. If none, it is looked up from info_dict.
2002 hashtree_info_generator: If present and image is sparse, generates the
2003 hashtree_info for this sparse image.
2004 reset_file_map: If true and image is sparse, reset file map before returning
2005 the image.
2006 Returns:
2007 A Image object. If it is a sparse image and reset_file_map is False, the
2008 image will have file_map info loaded.
2009 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002010 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002011 info_dict = LoadInfoDict(input_zip)
2012
2013 is_sparse = info_dict.get("extfs_sparse_flag")
2014
2015 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2016 # shared blocks (i.e. some blocks will show up in multiple files' block
2017 # list). We can only allocate such shared blocks to the first "owner", and
2018 # disable imgdiff for all later occurrences.
2019 if allow_shared_blocks is None:
2020 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2021
2022 if is_sparse:
2023 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2024 hashtree_info_generator)
2025 if reset_file_map:
2026 img.ResetFileMap()
2027 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002028 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002029
2030
2031def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2032 """Returns a Image object suitable for passing to BlockImageDiff.
2033
2034 This function loads the specified non-sparse image from the given path.
2035
2036 Args:
2037 which: The partition name.
2038 tmpdir: The directory that contains the prebuilt image and block map file.
2039 Returns:
2040 A Image object.
2041 """
2042 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2043 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2044
2045 # The image and map files must have been created prior to calling
2046 # ota_from_target_files.py (since LMP).
2047 assert os.path.exists(path) and os.path.exists(mappath)
2048
Tianjie Xu41976c72019-07-03 13:57:01 -07002049 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2050
Yifan Hong8a66a712019-04-04 15:37:57 -07002051
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002052def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2053 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002054 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2055
2056 This function loads the specified sparse image from the given path, and
2057 performs additional processing for OTA purpose. For example, it always adds
2058 block 0 to clobbered blocks list. It also detects files that cannot be
2059 reconstructed from the block list, for whom we should avoid applying imgdiff.
2060
2061 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002062 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002063 tmpdir: The directory that contains the prebuilt image and block map file.
2064 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002065 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002066 hashtree_info_generator: If present, generates the hashtree_info for this
2067 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002068 Returns:
2069 A SparseImage object, with file_map info loaded.
2070 """
Tao Baoc765cca2018-01-31 17:32:40 -08002071 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2072 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2073
2074 # The image and map files must have been created prior to calling
2075 # ota_from_target_files.py (since LMP).
2076 assert os.path.exists(path) and os.path.exists(mappath)
2077
2078 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2079 # it to clobbered_blocks so that it will be written to the target
2080 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2081 clobbered_blocks = "0"
2082
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002083 image = sparse_img.SparseImage(
2084 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2085 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002086
2087 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2088 # if they contain all zeros. We can't reconstruct such a file from its block
2089 # list. Tag such entries accordingly. (Bug: 65213616)
2090 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002091 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002092 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002093 continue
2094
Tom Cherryd14b8952018-08-09 14:26:00 -07002095 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2096 # filename listed in system.map may contain an additional leading slash
2097 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2098 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002099 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002100 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002101 arcname = entry.lstrip('/')
2102 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002103 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002104 else:
2105 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002106
2107 assert arcname in input_zip.namelist(), \
2108 "Failed to find the ZIP entry for {}".format(entry)
2109
Tao Baoc765cca2018-01-31 17:32:40 -08002110 info = input_zip.getinfo(arcname)
2111 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002112
2113 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002114 # image, check the original block list to determine its completeness. Note
2115 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002116 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002117 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002118
Tao Baoc765cca2018-01-31 17:32:40 -08002119 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2120 ranges.extra['incomplete'] = True
2121
2122 return image
2123
2124
Doug Zongkereef39442009-04-02 12:14:19 -07002125def GetKeyPasswords(keylist):
2126 """Given a list of keys, prompt the user to enter passwords for
2127 those which require them. Return a {key: password} dict. password
2128 will be None if the key has no password."""
2129
Doug Zongker8ce7c252009-05-22 13:34:54 -07002130 no_passwords = []
2131 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002132 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002133 devnull = open("/dev/null", "w+b")
2134 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002135 # We don't need a password for things that aren't really keys.
2136 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002137 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002138 continue
2139
T.R. Fullhart37e10522013-03-18 10:31:26 -07002140 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002141 "-inform", "DER", "-nocrypt"],
2142 stdin=devnull.fileno(),
2143 stdout=devnull.fileno(),
2144 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002145 p.communicate()
2146 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002147 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002148 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002149 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002150 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2151 "-inform", "DER", "-passin", "pass:"],
2152 stdin=devnull.fileno(),
2153 stdout=devnull.fileno(),
2154 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002155 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002156 if p.returncode == 0:
2157 # Encrypted key with empty string as password.
2158 key_passwords[k] = ''
2159 elif stderr.startswith('Error decrypting key'):
2160 # Definitely encrypted key.
2161 # It would have said "Error reading key" if it didn't parse correctly.
2162 need_passwords.append(k)
2163 else:
2164 # Potentially, a type of key that openssl doesn't understand.
2165 # We'll let the routines in signapk.jar handle it.
2166 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002167 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002168
T.R. Fullhart37e10522013-03-18 10:31:26 -07002169 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002170 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002171 return key_passwords
2172
2173
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002174def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002175 """Gets the minSdkVersion declared in the APK.
2176
changho.shin0f125362019-07-08 10:59:00 +09002177 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002178 This can be both a decimal number (API Level) or a codename.
2179
2180 Args:
2181 apk_name: The APK filename.
2182
2183 Returns:
2184 The parsed SDK version string.
2185
2186 Raises:
2187 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002188 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002189 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002190 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002191 stderr=subprocess.PIPE)
2192 stdoutdata, stderrdata = proc.communicate()
2193 if proc.returncode != 0:
2194 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002195 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002196 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002197
Tao Baof47bf0f2018-03-21 23:28:51 -07002198 for line in stdoutdata.split("\n"):
2199 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002200 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2201 if m:
2202 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002203 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002204
2205
2206def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002207 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002208
Tao Baof47bf0f2018-03-21 23:28:51 -07002209 If minSdkVersion is set to a codename, it is translated to a number using the
2210 provided map.
2211
2212 Args:
2213 apk_name: The APK filename.
2214
2215 Returns:
2216 The parsed SDK version number.
2217
2218 Raises:
2219 ExternalError: On failing to get the min SDK version number.
2220 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002221 version = GetMinSdkVersion(apk_name)
2222 try:
2223 return int(version)
2224 except ValueError:
2225 # Not a decimal number. Codename?
2226 if version in codename_to_api_level_map:
2227 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002228 raise ExternalError(
2229 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2230 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002231
2232
2233def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002234 codename_to_api_level_map=None, whole_file=False,
2235 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002236 """Sign the input_name zip/jar/apk, producing output_name. Use the
2237 given key and password (the latter may be None if the key does not
2238 have a password.
2239
Doug Zongker951495f2009-08-14 12:44:19 -07002240 If whole_file is true, use the "-w" option to SignApk to embed a
2241 signature that covers the whole file in the archive comment of the
2242 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002243
2244 min_api_level is the API Level (int) of the oldest platform this file may end
2245 up on. If not specified for an APK, the API Level is obtained by interpreting
2246 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2247
2248 codename_to_api_level_map is needed to translate the codename which may be
2249 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002250
2251 Caller may optionally specify extra args to be passed to SignApk, which
2252 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002253 """
Tao Bao76def242017-11-21 09:25:31 -08002254 if codename_to_api_level_map is None:
2255 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002256 if extra_signapk_args is None:
2257 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002258
Alex Klyubin9667b182015-12-10 13:38:50 -08002259 java_library_path = os.path.join(
2260 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2261
Tao Baoe95540e2016-11-08 12:08:53 -08002262 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2263 ["-Djava.library.path=" + java_library_path,
2264 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002265 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002266 if whole_file:
2267 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002268
2269 min_sdk_version = min_api_level
2270 if min_sdk_version is None:
2271 if not whole_file:
2272 min_sdk_version = GetMinSdkVersionInt(
2273 input_name, codename_to_api_level_map)
2274 if min_sdk_version is not None:
2275 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2276
T.R. Fullhart37e10522013-03-18 10:31:26 -07002277 cmd.extend([key + OPTIONS.public_key_suffix,
2278 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002279 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002280
Tao Bao73dd4f42018-10-04 16:25:33 -07002281 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002282 if password is not None:
2283 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002284 stdoutdata, _ = proc.communicate(password)
2285 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002286 raise ExternalError(
2287 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002288 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002289
Doug Zongkereef39442009-04-02 12:14:19 -07002290
Doug Zongker37974732010-09-16 17:44:38 -07002291def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002292 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002293
Tao Bao9dd909e2017-11-14 11:27:32 -08002294 For non-AVB images, raise exception if the data is too big. Print a warning
2295 if the data is nearing the maximum size.
2296
2297 For AVB images, the actual image size should be identical to the limit.
2298
2299 Args:
2300 data: A string that contains all the data for the partition.
2301 target: The partition name. The ".img" suffix is optional.
2302 info_dict: The dict to be looked up for relevant info.
2303 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002304 if target.endswith(".img"):
2305 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002306 mount_point = "/" + target
2307
Ying Wangf8824af2014-06-03 14:07:27 -07002308 fs_type = None
2309 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002310 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002311 if mount_point == "/userdata":
2312 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002313 p = info_dict["fstab"][mount_point]
2314 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002315 device = p.device
2316 if "/" in device:
2317 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002318 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002319 if not fs_type or not limit:
2320 return
Doug Zongkereef39442009-04-02 12:14:19 -07002321
Andrew Boie0f9aec82012-02-14 09:32:52 -08002322 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002323 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2324 # path.
2325 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2326 if size != limit:
2327 raise ExternalError(
2328 "Mismatching image size for %s: expected %d actual %d" % (
2329 target, limit, size))
2330 else:
2331 pct = float(size) * 100.0 / limit
2332 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2333 if pct >= 99.0:
2334 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002335
2336 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002337 logger.warning("\n WARNING: %s\n", msg)
2338 else:
2339 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002340
2341
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002342def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002343 """Parses the APK certs info from a given target-files zip.
2344
2345 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2346 tuple with the following elements: (1) a dictionary that maps packages to
2347 certs (based on the "certificate" and "private_key" attributes in the file;
2348 (2) a string representing the extension of compressed APKs in the target files
2349 (e.g ".gz", ".bro").
2350
2351 Args:
2352 tf_zip: The input target_files ZipFile (already open).
2353
2354 Returns:
2355 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2356 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2357 no compressed APKs.
2358 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002359 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002360 compressed_extension = None
2361
Tao Bao0f990332017-09-08 19:02:54 -07002362 # META/apkcerts.txt contains the info for _all_ the packages known at build
2363 # time. Filter out the ones that are not installed.
2364 installed_files = set()
2365 for name in tf_zip.namelist():
2366 basename = os.path.basename(name)
2367 if basename:
2368 installed_files.add(basename)
2369
Tao Baoda30cfa2017-12-01 16:19:46 -08002370 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002371 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002372 if not line:
2373 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002374 m = re.match(
2375 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002376 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2377 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002378 line)
2379 if not m:
2380 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002381
Tao Bao818ddf52018-01-05 11:17:34 -08002382 matches = m.groupdict()
2383 cert = matches["CERT"]
2384 privkey = matches["PRIVKEY"]
2385 name = matches["NAME"]
2386 this_compressed_extension = matches["COMPRESSED"]
2387
2388 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2389 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2390 if cert in SPECIAL_CERT_STRINGS and not privkey:
2391 certmap[name] = cert
2392 elif (cert.endswith(OPTIONS.public_key_suffix) and
2393 privkey.endswith(OPTIONS.private_key_suffix) and
2394 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2395 certmap[name] = cert[:-public_key_suffix_len]
2396 else:
2397 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2398
2399 if not this_compressed_extension:
2400 continue
2401
2402 # Only count the installed files.
2403 filename = name + '.' + this_compressed_extension
2404 if filename not in installed_files:
2405 continue
2406
2407 # Make sure that all the values in the compression map have the same
2408 # extension. We don't support multiple compression methods in the same
2409 # system image.
2410 if compressed_extension:
2411 if this_compressed_extension != compressed_extension:
2412 raise ValueError(
2413 "Multiple compressed extensions: {} vs {}".format(
2414 compressed_extension, this_compressed_extension))
2415 else:
2416 compressed_extension = this_compressed_extension
2417
2418 return (certmap,
2419 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002420
2421
Doug Zongkereef39442009-04-02 12:14:19 -07002422COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002423Global options
2424
2425 -p (--path) <dir>
2426 Prepend <dir>/bin to the list of places to search for binaries run by this
2427 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002428
Doug Zongker05d3dea2009-06-22 11:32:31 -07002429 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002430 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002431
Tao Bao30df8b42018-04-23 15:32:53 -07002432 -x (--extra) <key=value>
2433 Add a key/value pair to the 'extras' dict, which device-specific extension
2434 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002435
Doug Zongkereef39442009-04-02 12:14:19 -07002436 -v (--verbose)
2437 Show command lines being executed.
2438
2439 -h (--help)
2440 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002441
2442 --logfile <file>
2443 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002444"""
2445
Kelvin Zhang0876c412020-06-23 15:06:58 -04002446
Doug Zongkereef39442009-04-02 12:14:19 -07002447def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002448 print(docstring.rstrip("\n"))
2449 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002450
2451
2452def ParseOptions(argv,
2453 docstring,
2454 extra_opts="", extra_long_opts=(),
2455 extra_option_handler=None):
2456 """Parse the options in argv and return any arguments that aren't
2457 flags. docstring is the calling module's docstring, to be displayed
2458 for errors and -h. extra_opts and extra_long_opts are for flags
2459 defined by the caller, which are processed by passing them to
2460 extra_option_handler."""
2461
2462 try:
2463 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002464 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002465 ["help", "verbose", "path=", "signapk_path=",
2466 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002467 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002468 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2469 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002470 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2471 "aftl_key_path=", "aftl_manufacturer_key_path=",
2472 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002473 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002474 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002475 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002476 sys.exit(2)
2477
Doug Zongkereef39442009-04-02 12:14:19 -07002478 for o, a in opts:
2479 if o in ("-h", "--help"):
2480 Usage(docstring)
2481 sys.exit()
2482 elif o in ("-v", "--verbose"):
2483 OPTIONS.verbose = True
2484 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002485 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002486 elif o in ("--signapk_path",):
2487 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002488 elif o in ("--signapk_shared_library_path",):
2489 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002490 elif o in ("--extra_signapk_args",):
2491 OPTIONS.extra_signapk_args = shlex.split(a)
2492 elif o in ("--java_path",):
2493 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002494 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002495 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002496 elif o in ("--android_jar_path",):
2497 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002498 elif o in ("--public_key_suffix",):
2499 OPTIONS.public_key_suffix = a
2500 elif o in ("--private_key_suffix",):
2501 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002502 elif o in ("--boot_signer_path",):
2503 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002504 elif o in ("--boot_signer_args",):
2505 OPTIONS.boot_signer_args = shlex.split(a)
2506 elif o in ("--verity_signer_path",):
2507 OPTIONS.verity_signer_path = a
2508 elif o in ("--verity_signer_args",):
2509 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002510 elif o in ("--aftl_tool_path",):
2511 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002512 elif o in ("--aftl_server",):
2513 OPTIONS.aftl_server = a
2514 elif o in ("--aftl_key_path",):
2515 OPTIONS.aftl_key_path = a
2516 elif o in ("--aftl_manufacturer_key_path",):
2517 OPTIONS.aftl_manufacturer_key_path = a
2518 elif o in ("--aftl_signer_helper",):
2519 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002520 elif o in ("-s", "--device_specific"):
2521 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002522 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002523 key, value = a.split("=", 1)
2524 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002525 elif o in ("--logfile",):
2526 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002527 else:
2528 if extra_option_handler is None or not extra_option_handler(o, a):
2529 assert False, "unknown option \"%s\"" % (o,)
2530
Doug Zongker85448772014-09-09 14:59:20 -07002531 if OPTIONS.search_path:
2532 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2533 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002534
2535 return args
2536
2537
Tao Bao4c851b12016-09-19 13:54:38 -07002538def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002539 """Make a temp file and add it to the list of things to be deleted
2540 when Cleanup() is called. Return the filename."""
2541 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2542 os.close(fd)
2543 OPTIONS.tempfiles.append(fn)
2544 return fn
2545
2546
Tao Bao1c830bf2017-12-25 10:43:47 -08002547def MakeTempDir(prefix='tmp', suffix=''):
2548 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2549
2550 Returns:
2551 The absolute pathname of the new directory.
2552 """
2553 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2554 OPTIONS.tempfiles.append(dir_name)
2555 return dir_name
2556
2557
Doug Zongkereef39442009-04-02 12:14:19 -07002558def Cleanup():
2559 for i in OPTIONS.tempfiles:
2560 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002561 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002562 else:
2563 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002564 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002565
2566
2567class PasswordManager(object):
2568 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002569 self.editor = os.getenv("EDITOR")
2570 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002571
2572 def GetPasswords(self, items):
2573 """Get passwords corresponding to each string in 'items',
2574 returning a dict. (The dict may have keys in addition to the
2575 values in 'items'.)
2576
2577 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2578 user edit that file to add more needed passwords. If no editor is
2579 available, or $ANDROID_PW_FILE isn't define, prompts the user
2580 interactively in the ordinary way.
2581 """
2582
2583 current = self.ReadFile()
2584
2585 first = True
2586 while True:
2587 missing = []
2588 for i in items:
2589 if i not in current or not current[i]:
2590 missing.append(i)
2591 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002592 if not missing:
2593 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002594
2595 for i in missing:
2596 current[i] = ""
2597
2598 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002599 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002600 if sys.version_info[0] >= 3:
2601 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002602 answer = raw_input("try to edit again? [y]> ").strip()
2603 if answer and answer[0] not in 'yY':
2604 raise RuntimeError("key passwords unavailable")
2605 first = False
2606
2607 current = self.UpdateAndReadFile(current)
2608
Kelvin Zhang0876c412020-06-23 15:06:58 -04002609 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002610 """Prompt the user to enter a value (password) for each key in
2611 'current' whose value is fales. Returns a new dict with all the
2612 values.
2613 """
2614 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002615 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002616 if v:
2617 result[k] = v
2618 else:
2619 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002620 result[k] = getpass.getpass(
2621 "Enter password for %s key> " % k).strip()
2622 if result[k]:
2623 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002624 return result
2625
2626 def UpdateAndReadFile(self, current):
2627 if not self.editor or not self.pwfile:
2628 return self.PromptResult(current)
2629
2630 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002631 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002632 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2633 f.write("# (Additional spaces are harmless.)\n\n")
2634
2635 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002636 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002637 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002638 f.write("[[[ %s ]]] %s\n" % (v, k))
2639 if not v and first_line is None:
2640 # position cursor on first line with no password.
2641 first_line = i + 4
2642 f.close()
2643
Tao Bao986ee862018-10-04 15:46:16 -07002644 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002645
2646 return self.ReadFile()
2647
2648 def ReadFile(self):
2649 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002650 if self.pwfile is None:
2651 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002652 try:
2653 f = open(self.pwfile, "r")
2654 for line in f:
2655 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002656 if not line or line[0] == '#':
2657 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002658 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2659 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002660 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002661 else:
2662 result[m.group(2)] = m.group(1)
2663 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002664 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002665 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002666 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002667 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002668
2669
Dan Albert8e0178d2015-01-27 15:53:15 -08002670def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2671 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002672
2673 # http://b/18015246
2674 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2675 # for files larger than 2GiB. We can work around this by adjusting their
2676 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2677 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2678 # it isn't clear to me exactly what circumstances cause this).
2679 # `zipfile.write()` must be used directly to work around this.
2680 #
2681 # This mess can be avoided if we port to python3.
2682 saved_zip64_limit = zipfile.ZIP64_LIMIT
2683 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2684
2685 if compress_type is None:
2686 compress_type = zip_file.compression
2687 if arcname is None:
2688 arcname = filename
2689
2690 saved_stat = os.stat(filename)
2691
2692 try:
2693 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2694 # file to be zipped and reset it when we're done.
2695 os.chmod(filename, perms)
2696
2697 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002698 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2699 # intentional. zip stores datetimes in local time without a time zone
2700 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2701 # in the zip archive.
2702 local_epoch = datetime.datetime.fromtimestamp(0)
2703 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002704 os.utime(filename, (timestamp, timestamp))
2705
2706 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2707 finally:
2708 os.chmod(filename, saved_stat.st_mode)
2709 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2710 zipfile.ZIP64_LIMIT = saved_zip64_limit
2711
2712
Tao Bao58c1b962015-05-20 09:32:18 -07002713def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002714 compress_type=None):
2715 """Wrap zipfile.writestr() function to work around the zip64 limit.
2716
2717 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2718 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2719 when calling crc32(bytes).
2720
2721 But it still works fine to write a shorter string into a large zip file.
2722 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2723 when we know the string won't be too long.
2724 """
2725
2726 saved_zip64_limit = zipfile.ZIP64_LIMIT
2727 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2728
2729 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2730 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002731 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002732 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002733 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002734 else:
Tao Baof3282b42015-04-01 11:21:55 -07002735 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002736 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2737 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2738 # such a case (since
2739 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2740 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2741 # permission bits. We follow the logic in Python 3 to get consistent
2742 # behavior between using the two versions.
2743 if not zinfo.external_attr:
2744 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002745
2746 # If compress_type is given, it overrides the value in zinfo.
2747 if compress_type is not None:
2748 zinfo.compress_type = compress_type
2749
Tao Bao58c1b962015-05-20 09:32:18 -07002750 # If perms is given, it has a priority.
2751 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002752 # If perms doesn't set the file type, mark it as a regular file.
2753 if perms & 0o770000 == 0:
2754 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002755 zinfo.external_attr = perms << 16
2756
Tao Baof3282b42015-04-01 11:21:55 -07002757 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002758 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2759
Dan Albert8b72aef2015-03-23 19:13:21 -07002760 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002761 zipfile.ZIP64_LIMIT = saved_zip64_limit
2762
2763
Tao Bao89d7ab22017-12-14 17:05:33 -08002764def ZipDelete(zip_filename, entries):
2765 """Deletes entries from a ZIP file.
2766
2767 Since deleting entries from a ZIP file is not supported, it shells out to
2768 'zip -d'.
2769
2770 Args:
2771 zip_filename: The name of the ZIP file.
2772 entries: The name of the entry, or the list of names to be deleted.
2773
2774 Raises:
2775 AssertionError: In case of non-zero return from 'zip'.
2776 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002777 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002778 entries = [entries]
2779 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002780 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002781
2782
Tao Baof3282b42015-04-01 11:21:55 -07002783def ZipClose(zip_file):
2784 # http://b/18015246
2785 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2786 # central directory.
2787 saved_zip64_limit = zipfile.ZIP64_LIMIT
2788 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2789
2790 zip_file.close()
2791
2792 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002793
2794
2795class DeviceSpecificParams(object):
2796 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002797
Doug Zongker05d3dea2009-06-22 11:32:31 -07002798 def __init__(self, **kwargs):
2799 """Keyword arguments to the constructor become attributes of this
2800 object, which is passed to all functions in the device-specific
2801 module."""
Tao Bao38884282019-07-10 22:20:56 -07002802 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002803 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002804 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002805
2806 if self.module is None:
2807 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002808 if not path:
2809 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002810 try:
2811 if os.path.isdir(path):
2812 info = imp.find_module("releasetools", [path])
2813 else:
2814 d, f = os.path.split(path)
2815 b, x = os.path.splitext(f)
2816 if x == ".py":
2817 f = b
2818 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002819 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002820 self.module = imp.load_module("device_specific", *info)
2821 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002822 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002823
2824 def _DoCall(self, function_name, *args, **kwargs):
2825 """Call the named function in the device-specific module, passing
2826 the given args and kwargs. The first argument to the call will be
2827 the DeviceSpecific object itself. If there is no module, or the
2828 module does not define the function, return the value of the
2829 'default' kwarg (which itself defaults to None)."""
2830 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002831 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002832 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2833
2834 def FullOTA_Assertions(self):
2835 """Called after emitting the block of assertions at the top of a
2836 full OTA package. Implementations can add whatever additional
2837 assertions they like."""
2838 return self._DoCall("FullOTA_Assertions")
2839
Doug Zongkere5ff5902012-01-17 10:55:37 -08002840 def FullOTA_InstallBegin(self):
2841 """Called at the start of full OTA installation."""
2842 return self._DoCall("FullOTA_InstallBegin")
2843
Yifan Hong10c530d2018-12-27 17:34:18 -08002844 def FullOTA_GetBlockDifferences(self):
2845 """Called during full OTA installation and verification.
2846 Implementation should return a list of BlockDifference objects describing
2847 the update on each additional partitions.
2848 """
2849 return self._DoCall("FullOTA_GetBlockDifferences")
2850
Doug Zongker05d3dea2009-06-22 11:32:31 -07002851 def FullOTA_InstallEnd(self):
2852 """Called at the end of full OTA installation; typically this is
2853 used to install the image for the device's baseband processor."""
2854 return self._DoCall("FullOTA_InstallEnd")
2855
2856 def IncrementalOTA_Assertions(self):
2857 """Called after emitting the block of assertions at the top of an
2858 incremental OTA package. Implementations can add whatever
2859 additional assertions they like."""
2860 return self._DoCall("IncrementalOTA_Assertions")
2861
Doug Zongkere5ff5902012-01-17 10:55:37 -08002862 def IncrementalOTA_VerifyBegin(self):
2863 """Called at the start of the verification phase of incremental
2864 OTA installation; additional checks can be placed here to abort
2865 the script before any changes are made."""
2866 return self._DoCall("IncrementalOTA_VerifyBegin")
2867
Doug Zongker05d3dea2009-06-22 11:32:31 -07002868 def IncrementalOTA_VerifyEnd(self):
2869 """Called at the end of the verification phase of incremental OTA
2870 installation; additional checks can be placed here to abort the
2871 script before any changes are made."""
2872 return self._DoCall("IncrementalOTA_VerifyEnd")
2873
Doug Zongkere5ff5902012-01-17 10:55:37 -08002874 def IncrementalOTA_InstallBegin(self):
2875 """Called at the start of incremental OTA installation (after
2876 verification is complete)."""
2877 return self._DoCall("IncrementalOTA_InstallBegin")
2878
Yifan Hong10c530d2018-12-27 17:34:18 -08002879 def IncrementalOTA_GetBlockDifferences(self):
2880 """Called during incremental OTA installation and verification.
2881 Implementation should return a list of BlockDifference objects describing
2882 the update on each additional partitions.
2883 """
2884 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2885
Doug Zongker05d3dea2009-06-22 11:32:31 -07002886 def IncrementalOTA_InstallEnd(self):
2887 """Called at the end of incremental OTA installation; typically
2888 this is used to install the image for the device's baseband
2889 processor."""
2890 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002891
Tao Bao9bc6bb22015-11-09 16:58:28 -08002892 def VerifyOTA_Assertions(self):
2893 return self._DoCall("VerifyOTA_Assertions")
2894
Tao Bao76def242017-11-21 09:25:31 -08002895
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002896class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002897 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002898 self.name = name
2899 self.data = data
2900 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002901 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002902 self.sha1 = sha1(data).hexdigest()
2903
2904 @classmethod
2905 def FromLocalFile(cls, name, diskname):
2906 f = open(diskname, "rb")
2907 data = f.read()
2908 f.close()
2909 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002910
2911 def WriteToTemp(self):
2912 t = tempfile.NamedTemporaryFile()
2913 t.write(self.data)
2914 t.flush()
2915 return t
2916
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002917 def WriteToDir(self, d):
2918 with open(os.path.join(d, self.name), "wb") as fp:
2919 fp.write(self.data)
2920
Geremy Condra36bd3652014-02-06 19:45:10 -08002921 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002922 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002923
Tao Bao76def242017-11-21 09:25:31 -08002924
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002925DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002926 ".gz": "imgdiff",
2927 ".zip": ["imgdiff", "-z"],
2928 ".jar": ["imgdiff", "-z"],
2929 ".apk": ["imgdiff", "-z"],
2930 ".img": "imgdiff",
2931}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002932
Tao Bao76def242017-11-21 09:25:31 -08002933
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002934class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002935 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002936 self.tf = tf
2937 self.sf = sf
2938 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002939 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002940
2941 def ComputePatch(self):
2942 """Compute the patch (as a string of data) needed to turn sf into
2943 tf. Returns the same tuple as GetPatch()."""
2944
2945 tf = self.tf
2946 sf = self.sf
2947
Doug Zongker24cd2802012-08-14 16:36:15 -07002948 if self.diff_program:
2949 diff_program = self.diff_program
2950 else:
2951 ext = os.path.splitext(tf.name)[1]
2952 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002953
2954 ttemp = tf.WriteToTemp()
2955 stemp = sf.WriteToTemp()
2956
2957 ext = os.path.splitext(tf.name)[1]
2958
2959 try:
2960 ptemp = tempfile.NamedTemporaryFile()
2961 if isinstance(diff_program, list):
2962 cmd = copy.copy(diff_program)
2963 else:
2964 cmd = [diff_program]
2965 cmd.append(stemp.name)
2966 cmd.append(ttemp.name)
2967 cmd.append(ptemp.name)
2968 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002969 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002970
Doug Zongkerf8340082014-08-05 10:39:37 -07002971 def run():
2972 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002973 if e:
2974 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002975 th = threading.Thread(target=run)
2976 th.start()
2977 th.join(timeout=300) # 5 mins
2978 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002979 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002980 p.terminate()
2981 th.join(5)
2982 if th.is_alive():
2983 p.kill()
2984 th.join()
2985
Tianjie Xua2a9f992018-01-05 15:15:54 -08002986 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002987 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002988 self.patch = None
2989 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002990 diff = ptemp.read()
2991 finally:
2992 ptemp.close()
2993 stemp.close()
2994 ttemp.close()
2995
2996 self.patch = diff
2997 return self.tf, self.sf, self.patch
2998
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002999 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003000 """Returns a tuple of (target_file, source_file, patch_data).
3001
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003002 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003003 computing the patch failed.
3004 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003005 return self.tf, self.sf, self.patch
3006
3007
3008def ComputeDifferences(diffs):
3009 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003010 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003011
3012 # Do the largest files first, to try and reduce the long-pole effect.
3013 by_size = [(i.tf.size, i) for i in diffs]
3014 by_size.sort(reverse=True)
3015 by_size = [i[1] for i in by_size]
3016
3017 lock = threading.Lock()
3018 diff_iter = iter(by_size) # accessed under lock
3019
3020 def worker():
3021 try:
3022 lock.acquire()
3023 for d in diff_iter:
3024 lock.release()
3025 start = time.time()
3026 d.ComputePatch()
3027 dur = time.time() - start
3028 lock.acquire()
3029
3030 tf, sf, patch = d.GetPatch()
3031 if sf.name == tf.name:
3032 name = tf.name
3033 else:
3034 name = "%s (%s)" % (tf.name, sf.name)
3035 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003036 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003037 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003038 logger.info(
3039 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3040 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003041 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003042 except Exception:
3043 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003044 raise
3045
3046 # start worker threads; wait for them all to finish.
3047 threads = [threading.Thread(target=worker)
3048 for i in range(OPTIONS.worker_threads)]
3049 for th in threads:
3050 th.start()
3051 while threads:
3052 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003053
3054
Dan Albert8b72aef2015-03-23 19:13:21 -07003055class BlockDifference(object):
3056 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003057 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003058 self.tgt = tgt
3059 self.src = src
3060 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003061 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003062 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003063
Tao Baodd2a5892015-03-12 12:32:37 -07003064 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003065 version = max(
3066 int(i) for i in
3067 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003068 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003069 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003070
Tianjie Xu41976c72019-07-03 13:57:01 -07003071 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3072 version=self.version,
3073 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003074 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003075 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003076 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003077 self.touched_src_ranges = b.touched_src_ranges
3078 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003079
Yifan Hong10c530d2018-12-27 17:34:18 -08003080 # On devices with dynamic partitions, for new partitions,
3081 # src is None but OPTIONS.source_info_dict is not.
3082 if OPTIONS.source_info_dict is None:
3083 is_dynamic_build = OPTIONS.info_dict.get(
3084 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003085 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003086 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003087 is_dynamic_build = OPTIONS.source_info_dict.get(
3088 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003089 is_dynamic_source = partition in shlex.split(
3090 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003091
Yifan Hongbb2658d2019-01-25 12:30:58 -08003092 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003093 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3094
Yifan Hongbb2658d2019-01-25 12:30:58 -08003095 # For dynamic partitions builds, check partition list in both source
3096 # and target build because new partitions may be added, and existing
3097 # partitions may be removed.
3098 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3099
Yifan Hong10c530d2018-12-27 17:34:18 -08003100 if is_dynamic:
3101 self.device = 'map_partition("%s")' % partition
3102 else:
3103 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003104 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3105 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003106 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003107 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3108 OPTIONS.source_info_dict)
3109 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003110
Tao Baod8d14be2016-02-04 14:26:02 -08003111 @property
3112 def required_cache(self):
3113 return self._required_cache
3114
Tao Bao76def242017-11-21 09:25:31 -08003115 def WriteScript(self, script, output_zip, progress=None,
3116 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003117 if not self.src:
3118 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003119 script.Print("Patching %s image unconditionally..." % (self.partition,))
3120 else:
3121 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003122
Dan Albert8b72aef2015-03-23 19:13:21 -07003123 if progress:
3124 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003125 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003126
3127 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003128 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003129
Tao Bao9bc6bb22015-11-09 16:58:28 -08003130 def WriteStrictVerifyScript(self, script):
3131 """Verify all the blocks in the care_map, including clobbered blocks.
3132
3133 This differs from the WriteVerifyScript() function: a) it prints different
3134 error messages; b) it doesn't allow half-way updated images to pass the
3135 verification."""
3136
3137 partition = self.partition
3138 script.Print("Verifying %s..." % (partition,))
3139 ranges = self.tgt.care_map
3140 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003141 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003142 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3143 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003144 self.device, ranges_str,
3145 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003146 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003147 script.AppendExtra("")
3148
Tao Baod522bdc2016-04-12 15:53:16 -07003149 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003150 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003151
3152 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003153 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003154 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003155
3156 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003157 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003158 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003159 ranges = self.touched_src_ranges
3160 expected_sha1 = self.touched_src_sha1
3161 else:
3162 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3163 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003164
3165 # No blocks to be checked, skipping.
3166 if not ranges:
3167 return
3168
Tao Bao5ece99d2015-05-12 11:42:31 -07003169 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003170 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003171 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003172 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3173 '"%s.patch.dat")) then' % (
3174 self.device, ranges_str, expected_sha1,
3175 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003176 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003177 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003178
Tianjie Xufc3422a2015-12-15 11:53:59 -08003179 if self.version >= 4:
3180
3181 # Bug: 21124327
3182 # When generating incrementals for the system and vendor partitions in
3183 # version 4 or newer, explicitly check the first block (which contains
3184 # the superblock) of the partition to see if it's what we expect. If
3185 # this check fails, give an explicit log message about the partition
3186 # having been remounted R/W (the most likely explanation).
3187 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003188 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003189
3190 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003191 if partition == "system":
3192 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3193 else:
3194 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003195 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003196 'ifelse (block_image_recover({device}, "{ranges}") && '
3197 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003198 'package_extract_file("{partition}.transfer.list"), '
3199 '"{partition}.new.dat", "{partition}.patch.dat"), '
3200 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003201 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003202 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003203 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003204
Tao Baodd2a5892015-03-12 12:32:37 -07003205 # Abort the OTA update. Note that the incremental OTA cannot be applied
3206 # even if it may match the checksum of the target partition.
3207 # a) If version < 3, operations like move and erase will make changes
3208 # unconditionally and damage the partition.
3209 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003210 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003211 if partition == "system":
3212 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3213 else:
3214 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3215 script.AppendExtra((
3216 'abort("E%d: %s partition has unexpected contents");\n'
3217 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003218
Yifan Hong10c530d2018-12-27 17:34:18 -08003219 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003220 partition = self.partition
3221 script.Print('Verifying the updated %s image...' % (partition,))
3222 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3223 ranges = self.tgt.care_map
3224 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003225 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003226 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003227 self.device, ranges_str,
3228 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003229
3230 # Bug: 20881595
3231 # Verify that extended blocks are really zeroed out.
3232 if self.tgt.extended:
3233 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003234 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003235 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003236 self.device, ranges_str,
3237 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003238 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003239 if partition == "system":
3240 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3241 else:
3242 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003243 script.AppendExtra(
3244 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003245 ' abort("E%d: %s partition has unexpected non-zero contents after '
3246 'OTA update");\n'
3247 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003248 else:
3249 script.Print('Verified the updated %s image.' % (partition,))
3250
Tianjie Xu209db462016-05-24 17:34:52 -07003251 if partition == "system":
3252 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3253 else:
3254 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3255
Tao Bao5fcaaef2015-06-01 13:40:49 -07003256 script.AppendExtra(
3257 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003258 ' abort("E%d: %s partition has unexpected contents after OTA '
3259 'update");\n'
3260 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003261
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003262 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003263 ZipWrite(output_zip,
3264 '{}.transfer.list'.format(self.path),
3265 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003266
Tao Bao76def242017-11-21 09:25:31 -08003267 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3268 # its size. Quailty 9 almost triples the compression time but doesn't
3269 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003270 # zip | brotli(quality 6) | brotli(quality 9)
3271 # compressed_size: 942M | 869M (~8% reduced) | 854M
3272 # compression_time: 75s | 265s | 719s
3273 # decompression_time: 15s | 25s | 25s
3274
3275 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003276 brotli_cmd = ['brotli', '--quality=6',
3277 '--output={}.new.dat.br'.format(self.path),
3278 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003279 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003280 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003281
3282 new_data_name = '{}.new.dat.br'.format(self.partition)
3283 ZipWrite(output_zip,
3284 '{}.new.dat.br'.format(self.path),
3285 new_data_name,
3286 compress_type=zipfile.ZIP_STORED)
3287 else:
3288 new_data_name = '{}.new.dat'.format(self.partition)
3289 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3290
Dan Albert8e0178d2015-01-27 15:53:15 -08003291 ZipWrite(output_zip,
3292 '{}.patch.dat'.format(self.path),
3293 '{}.patch.dat'.format(self.partition),
3294 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003295
Tianjie Xu209db462016-05-24 17:34:52 -07003296 if self.partition == "system":
3297 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3298 else:
3299 code = ErrorCode.VENDOR_UPDATE_FAILURE
3300
Yifan Hong10c530d2018-12-27 17:34:18 -08003301 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003302 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003303 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003304 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003305 device=self.device, partition=self.partition,
3306 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003307 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003308
Kelvin Zhang0876c412020-06-23 15:06:58 -04003309 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003310 data = source.ReadRangeSet(ranges)
3311 ctx = sha1()
3312
3313 for p in data:
3314 ctx.update(p)
3315
3316 return ctx.hexdigest()
3317
Kelvin Zhang0876c412020-06-23 15:06:58 -04003318 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003319 """Return the hash value for all zero blocks."""
3320 zero_block = '\x00' * 4096
3321 ctx = sha1()
3322 for _ in range(num_blocks):
3323 ctx.update(zero_block)
3324
3325 return ctx.hexdigest()
3326
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003327
Tianjie Xu41976c72019-07-03 13:57:01 -07003328# Expose these two classes to support vendor-specific scripts
3329DataImage = images.DataImage
3330EmptyImage = images.EmptyImage
3331
Tao Bao76def242017-11-21 09:25:31 -08003332
Doug Zongker96a57e72010-09-26 14:57:41 -07003333# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003334PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003335 "ext4": "EMMC",
3336 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003337 "f2fs": "EMMC",
3338 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003339}
Doug Zongker96a57e72010-09-26 14:57:41 -07003340
Kelvin Zhang0876c412020-06-23 15:06:58 -04003341
Yifan Hongbdb32012020-05-07 12:38:53 -07003342def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3343 """
3344 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3345 backwards compatibility. It aborts if the fstab entry has slotselect option
3346 (unless check_no_slot is explicitly set to False).
3347 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003348 fstab = info["fstab"]
3349 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003350 if check_no_slot:
3351 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003352 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003353 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3354 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003355 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003356
3357
Yifan Hongbdb32012020-05-07 12:38:53 -07003358def GetTypeAndDeviceExpr(mount_point, info):
3359 """
3360 Return the filesystem of the partition, and an edify expression that evaluates
3361 to the device at runtime.
3362 """
3363 fstab = info["fstab"]
3364 if fstab:
3365 p = fstab[mount_point]
3366 device_expr = '"%s"' % fstab[mount_point].device
3367 if p.slotselect:
3368 device_expr = 'add_slot_suffix(%s)' % device_expr
3369 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003370 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003371
3372
3373def GetEntryForDevice(fstab, device):
3374 """
3375 Returns:
3376 The first entry in fstab whose device is the given value.
3377 """
3378 if not fstab:
3379 return None
3380 for mount_point in fstab:
3381 if fstab[mount_point].device == device:
3382 return fstab[mount_point]
3383 return None
3384
Kelvin Zhang0876c412020-06-23 15:06:58 -04003385
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003386def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003387 """Parses and converts a PEM-encoded certificate into DER-encoded.
3388
3389 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3390
3391 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003392 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003393 """
3394 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003395 save = False
3396 for line in data.split("\n"):
3397 if "--END CERTIFICATE--" in line:
3398 break
3399 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003400 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003401 if "--BEGIN CERTIFICATE--" in line:
3402 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003403 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003404 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003405
Tao Bao04e1f012018-02-04 12:13:35 -08003406
3407def ExtractPublicKey(cert):
3408 """Extracts the public key (PEM-encoded) from the given certificate file.
3409
3410 Args:
3411 cert: The certificate filename.
3412
3413 Returns:
3414 The public key string.
3415
3416 Raises:
3417 AssertionError: On non-zero return from 'openssl'.
3418 """
3419 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3420 # While openssl 1.1 writes the key into the given filename followed by '-out',
3421 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3422 # stdout instead.
3423 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3424 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3425 pubkey, stderrdata = proc.communicate()
3426 assert proc.returncode == 0, \
3427 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3428 return pubkey
3429
3430
Tao Bao1ac886e2019-06-26 11:58:22 -07003431def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003432 """Extracts the AVB public key from the given public or private key.
3433
3434 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003435 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003436 key: The input key file, which should be PEM-encoded public or private key.
3437
3438 Returns:
3439 The path to the extracted AVB public key file.
3440 """
3441 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3442 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003443 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003444 return output
3445
3446
Doug Zongker412c02f2014-02-13 10:58:24 -08003447def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3448 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003449 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003450
Tao Bao6d5d6232018-03-09 17:04:42 -08003451 Most of the space in the boot and recovery images is just the kernel, which is
3452 identical for the two, so the resulting patch should be efficient. Add it to
3453 the output zip, along with a shell script that is run from init.rc on first
3454 boot to actually do the patching and install the new recovery image.
3455
3456 Args:
3457 input_dir: The top-level input directory of the target-files.zip.
3458 output_sink: The callback function that writes the result.
3459 recovery_img: File object for the recovery image.
3460 boot_img: File objects for the boot image.
3461 info_dict: A dict returned by common.LoadInfoDict() on the input
3462 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003463 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003464 if info_dict is None:
3465 info_dict = OPTIONS.info_dict
3466
Tao Bao6d5d6232018-03-09 17:04:42 -08003467 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003468 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3469
3470 if board_uses_vendorimage:
3471 # In this case, the output sink is rooted at VENDOR
3472 recovery_img_path = "etc/recovery.img"
3473 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3474 sh_dir = "bin"
3475 else:
3476 # In this case the output sink is rooted at SYSTEM
3477 recovery_img_path = "vendor/etc/recovery.img"
3478 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3479 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003480
Tao Baof2cffbd2015-07-22 12:33:18 -07003481 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003482 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003483
3484 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003485 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003486 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003487 # With system-root-image, boot and recovery images will have mismatching
3488 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3489 # to handle such a case.
3490 if system_root_image:
3491 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003492 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003493 assert not os.path.exists(path)
3494 else:
3495 diff_program = ["imgdiff"]
3496 if os.path.exists(path):
3497 diff_program.append("-b")
3498 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003499 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003500 else:
3501 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003502
3503 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3504 _, _, patch = d.ComputePatch()
3505 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003506
Dan Albertebb19aa2015-03-27 19:11:53 -07003507 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003508 # The following GetTypeAndDevice()s need to use the path in the target
3509 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003510 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3511 check_no_slot=False)
3512 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3513 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003514 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003515 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003516
Tao Baof2cffbd2015-07-22 12:33:18 -07003517 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003518
3519 # Note that we use /vendor to refer to the recovery resources. This will
3520 # work for a separate vendor partition mounted at /vendor or a
3521 # /system/vendor subdirectory on the system partition, for which init will
3522 # create a symlink from /vendor to /system/vendor.
3523
3524 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003525if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3526 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003527 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003528 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3529 log -t recovery "Installing new recovery image: succeeded" || \\
3530 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003531else
3532 log -t recovery "Recovery image already installed"
3533fi
3534""" % {'type': recovery_type,
3535 'device': recovery_device,
3536 'sha1': recovery_img.sha1,
3537 'size': recovery_img.size}
3538 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003539 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003540if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3541 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003542 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003543 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3544 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3545 log -t recovery "Installing new recovery image: succeeded" || \\
3546 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003547else
3548 log -t recovery "Recovery image already installed"
3549fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003550""" % {'boot_size': boot_img.size,
3551 'boot_sha1': boot_img.sha1,
3552 'recovery_size': recovery_img.size,
3553 'recovery_sha1': recovery_img.sha1,
3554 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003555 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003556 'recovery_type': recovery_type,
3557 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003558 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003559
Bill Peckhame868aec2019-09-17 17:06:47 -07003560 # The install script location moved from /system/etc to /system/bin in the L
3561 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3562 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003563
Tao Bao32fcdab2018-10-12 10:30:39 -07003564 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003565
Tao Baoda30cfa2017-12-01 16:19:46 -08003566 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003567
3568
3569class DynamicPartitionUpdate(object):
3570 def __init__(self, src_group=None, tgt_group=None, progress=None,
3571 block_difference=None):
3572 self.src_group = src_group
3573 self.tgt_group = tgt_group
3574 self.progress = progress
3575 self.block_difference = block_difference
3576
3577 @property
3578 def src_size(self):
3579 if not self.block_difference:
3580 return 0
3581 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3582
3583 @property
3584 def tgt_size(self):
3585 if not self.block_difference:
3586 return 0
3587 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3588
3589 @staticmethod
3590 def _GetSparseImageSize(img):
3591 if not img:
3592 return 0
3593 return img.blocksize * img.total_blocks
3594
3595
3596class DynamicGroupUpdate(object):
3597 def __init__(self, src_size=None, tgt_size=None):
3598 # None: group does not exist. 0: no size limits.
3599 self.src_size = src_size
3600 self.tgt_size = tgt_size
3601
3602
3603class DynamicPartitionsDifference(object):
3604 def __init__(self, info_dict, block_diffs, progress_dict=None,
3605 source_info_dict=None):
3606 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003607 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003608
3609 self._remove_all_before_apply = False
3610 if source_info_dict is None:
3611 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003612 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003613
Tao Baof1113e92019-06-18 12:10:14 -07003614 block_diff_dict = collections.OrderedDict(
3615 [(e.partition, e) for e in block_diffs])
3616
Yifan Hong10c530d2018-12-27 17:34:18 -08003617 assert len(block_diff_dict) == len(block_diffs), \
3618 "Duplicated BlockDifference object for {}".format(
3619 [partition for partition, count in
3620 collections.Counter(e.partition for e in block_diffs).items()
3621 if count > 1])
3622
Yifan Hong79997e52019-01-23 16:56:19 -08003623 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003624
3625 for p, block_diff in block_diff_dict.items():
3626 self._partition_updates[p] = DynamicPartitionUpdate()
3627 self._partition_updates[p].block_difference = block_diff
3628
3629 for p, progress in progress_dict.items():
3630 if p in self._partition_updates:
3631 self._partition_updates[p].progress = progress
3632
3633 tgt_groups = shlex.split(info_dict.get(
3634 "super_partition_groups", "").strip())
3635 src_groups = shlex.split(source_info_dict.get(
3636 "super_partition_groups", "").strip())
3637
3638 for g in tgt_groups:
3639 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003640 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003641 assert p in self._partition_updates, \
3642 "{} is in target super_{}_partition_list but no BlockDifference " \
3643 "object is provided.".format(p, g)
3644 self._partition_updates[p].tgt_group = g
3645
3646 for g in src_groups:
3647 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003648 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003649 assert p in self._partition_updates, \
3650 "{} is in source super_{}_partition_list but no BlockDifference " \
3651 "object is provided.".format(p, g)
3652 self._partition_updates[p].src_group = g
3653
Yifan Hong45433e42019-01-18 13:55:25 -08003654 target_dynamic_partitions = set(shlex.split(info_dict.get(
3655 "dynamic_partition_list", "").strip()))
3656 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3657 if u.tgt_size)
3658 assert block_diffs_with_target == target_dynamic_partitions, \
3659 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3660 list(target_dynamic_partitions), list(block_diffs_with_target))
3661
3662 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3663 "dynamic_partition_list", "").strip()))
3664 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3665 if u.src_size)
3666 assert block_diffs_with_source == source_dynamic_partitions, \
3667 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3668 list(source_dynamic_partitions), list(block_diffs_with_source))
3669
Yifan Hong10c530d2018-12-27 17:34:18 -08003670 if self._partition_updates:
3671 logger.info("Updating dynamic partitions %s",
3672 self._partition_updates.keys())
3673
Yifan Hong79997e52019-01-23 16:56:19 -08003674 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003675
3676 for g in tgt_groups:
3677 self._group_updates[g] = DynamicGroupUpdate()
3678 self._group_updates[g].tgt_size = int(info_dict.get(
3679 "super_%s_group_size" % g, "0").strip())
3680
3681 for g in src_groups:
3682 if g not in self._group_updates:
3683 self._group_updates[g] = DynamicGroupUpdate()
3684 self._group_updates[g].src_size = int(source_info_dict.get(
3685 "super_%s_group_size" % g, "0").strip())
3686
3687 self._Compute()
3688
3689 def WriteScript(self, script, output_zip, write_verify_script=False):
3690 script.Comment('--- Start patching dynamic partitions ---')
3691 for p, u in self._partition_updates.items():
3692 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3693 script.Comment('Patch partition %s' % p)
3694 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3695 write_verify_script=False)
3696
3697 op_list_path = MakeTempFile()
3698 with open(op_list_path, 'w') as f:
3699 for line in self._op_list:
3700 f.write('{}\n'.format(line))
3701
3702 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3703
3704 script.Comment('Update dynamic partition metadata')
3705 script.AppendExtra('assert(update_dynamic_partitions('
3706 'package_extract_file("dynamic_partitions_op_list")));')
3707
3708 if write_verify_script:
3709 for p, u in self._partition_updates.items():
3710 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3711 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003712 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003713
3714 for p, u in self._partition_updates.items():
3715 if u.tgt_size and u.src_size <= u.tgt_size:
3716 script.Comment('Patch partition %s' % p)
3717 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3718 write_verify_script=write_verify_script)
3719 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003720 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003721
3722 script.Comment('--- End patching dynamic partitions ---')
3723
3724 def _Compute(self):
3725 self._op_list = list()
3726
3727 def append(line):
3728 self._op_list.append(line)
3729
3730 def comment(line):
3731 self._op_list.append("# %s" % line)
3732
3733 if self._remove_all_before_apply:
3734 comment('Remove all existing dynamic partitions and groups before '
3735 'applying full OTA')
3736 append('remove_all_groups')
3737
3738 for p, u in self._partition_updates.items():
3739 if u.src_group and not u.tgt_group:
3740 append('remove %s' % p)
3741
3742 for p, u in self._partition_updates.items():
3743 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3744 comment('Move partition %s from %s to default' % (p, u.src_group))
3745 append('move %s default' % p)
3746
3747 for p, u in self._partition_updates.items():
3748 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3749 comment('Shrink partition %s from %d to %d' %
3750 (p, u.src_size, u.tgt_size))
3751 append('resize %s %s' % (p, u.tgt_size))
3752
3753 for g, u in self._group_updates.items():
3754 if u.src_size is not None and u.tgt_size is None:
3755 append('remove_group %s' % g)
3756 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003757 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003758 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3759 append('resize_group %s %d' % (g, u.tgt_size))
3760
3761 for g, u in self._group_updates.items():
3762 if u.src_size is None and u.tgt_size is not None:
3763 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3764 append('add_group %s %d' % (g, u.tgt_size))
3765 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003766 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003767 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3768 append('resize_group %s %d' % (g, u.tgt_size))
3769
3770 for p, u in self._partition_updates.items():
3771 if u.tgt_group and not u.src_group:
3772 comment('Add partition %s to group %s' % (p, u.tgt_group))
3773 append('add %s %s' % (p, u.tgt_group))
3774
3775 for p, u in self._partition_updates.items():
3776 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003777 comment('Grow partition %s from %d to %d' %
3778 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003779 append('resize %s %d' % (p, u.tgt_size))
3780
3781 for p, u in self._partition_updates.items():
3782 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3783 comment('Move partition %s from default to %s' %
3784 (p, u.tgt_group))
3785 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003786
3787
jiajia tangf3f842b2021-03-17 21:49:44 +08003788def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003789 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003790 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003791
3792 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003793 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003794
3795 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003796 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003797 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003798 tmp_dir = MakeTempDir('boot_', suffix='.img')
3799 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003800 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3801 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003802 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3803 if not os.path.isfile(ramdisk):
3804 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3805 return None
3806 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003807 if ramdisk_format == RamdiskFormat.LZ4:
3808 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3809 elif ramdisk_format == RamdiskFormat.GZ:
3810 with open(ramdisk, 'rb') as input_stream:
3811 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003812 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3813 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003814 p2.wait()
3815 else:
3816 logger.error('Only support lz4 or minigzip ramdisk format.')
3817 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003818
3819 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3820 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3821 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3822 # the host environment.
3823 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003824 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003825
Yifan Hongc65a0542021-01-07 14:21:01 -08003826 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3827 prop_file = os.path.join(extracted_ramdisk, search_path)
3828 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003829 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003830 logger.warning(
3831 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003832
Yifan Hong7dc51172021-01-12 11:27:39 -08003833 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003834
Yifan Hong85ac5012021-01-07 14:43:46 -08003835 except ExternalError as e:
3836 logger.warning('Unable to get boot image build props: %s', e)
3837 return None
3838
3839
3840def GetBootImageTimestamp(boot_img):
3841 """
3842 Get timestamp from ramdisk within the boot image
3843
3844 Args:
3845 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3846
3847 Return:
3848 An integer that corresponds to the timestamp of the boot image, or None
3849 if file has unknown format. Raise exception if an unexpected error has
3850 occurred.
3851 """
3852 prop_file = GetBootImageBuildProp(boot_img)
3853 if not prop_file:
3854 return None
3855
3856 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3857 if props is None:
3858 return None
3859
3860 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003861 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3862 if timestamp:
3863 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003864 logger.warning(
3865 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003866 return None
3867
3868 except ExternalError as e:
3869 logger.warning('Unable to get boot image timestamp: %s', e)
3870 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003871
3872
3873def GetCareMap(which, imgname):
3874 """Returns the care_map string for the given partition.
3875
3876 Args:
3877 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3878 imgname: The filename of the image.
3879
3880 Returns:
3881 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3882 RangeSet; or None.
3883 """
3884 assert which in PARTITIONS_WITH_CARE_MAP
3885
3886 # which + "_image_size" contains the size that the actual filesystem image
3887 # resides in, which is all that needs to be verified. The additional blocks in
3888 # the image file contain verity metadata, by reading which would trigger
3889 # invalid reads.
3890 image_size = OPTIONS.info_dict.get(which + "_image_size")
3891 if not image_size:
3892 return None
3893
3894 image_blocks = int(image_size) // 4096 - 1
3895 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3896
3897 # For sparse images, we will only check the blocks that are listed in the care
3898 # map, i.e. the ones with meaningful data.
3899 if "extfs_sparse_flag" in OPTIONS.info_dict:
3900 simg = sparse_img.SparseImage(imgname)
3901 care_map_ranges = simg.care_map.intersect(
3902 rangelib.RangeSet("0-{}".format(image_blocks)))
3903
3904 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3905 # image.
3906 else:
3907 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3908
3909 return [which, care_map_ranges.to_string_raw()]
3910
3911
3912def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3913 """Generates and adds care_map.pb for a/b partition that has care_map.
3914
3915 Args:
3916 output_file: The output zip file (needs to be already open),
3917 or file path to write care_map.pb.
3918 ab_partitions: The list of A/B partitions.
3919 image_paths: A map from the partition name to the image path.
3920 """
3921 if not output_file:
3922 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3923
3924 care_map_list = []
3925 for partition in ab_partitions:
3926 partition = partition.strip()
3927 if partition not in PARTITIONS_WITH_CARE_MAP:
3928 continue
3929
3930 verity_block_device = "{}_verity_block_device".format(partition)
3931 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3932 if (verity_block_device in OPTIONS.info_dict or
3933 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3934 if partition not in image_paths:
3935 logger.warning('Potential partition with care_map missing from images: %s',
3936 partition)
3937 continue
3938 image_path = image_paths[partition]
3939 if not os.path.exists(image_path):
3940 raise ExternalError('Expected image at path {}'.format(image_path))
3941
3942 care_map = GetCareMap(partition, image_path)
3943 if not care_map:
3944 continue
3945 care_map_list += care_map
3946
3947 # adds fingerprint field to the care_map
3948 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3949 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3950 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3951 "ro.{}.build.thumbprint".format(partition)]
3952
3953 present_props = [x for x in prop_name_list if
3954 partition_props and partition_props.GetProp(x)]
3955 if not present_props:
3956 logger.warning(
3957 "fingerprint is not present for partition %s", partition)
3958 property_id, fingerprint = "unknown", "unknown"
3959 else:
3960 property_id = present_props[0]
3961 fingerprint = partition_props.GetProp(property_id)
3962 care_map_list += [property_id, fingerprint]
3963
3964 if not care_map_list:
3965 return
3966
3967 # Converts the list into proto buf message by calling care_map_generator; and
3968 # writes the result to a temp file.
3969 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3970 suffix=".txt")
3971 with open(temp_care_map_text, 'w') as text_file:
3972 text_file.write('\n'.join(care_map_list))
3973
3974 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3975 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3976 RunAndCheckOutput(care_map_gen_cmd)
3977
3978 if not isinstance(output_file, zipfile.ZipFile):
3979 shutil.copy(temp_care_map, output_file)
3980 return
3981 # output_file is a zip file
3982 care_map_path = "META/care_map.pb"
3983 if care_map_path in output_file.namelist():
3984 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3985 # replace_updated_files_list used by add_img_to_target_files
3986 if not OPTIONS.replace_updated_files_list:
3987 OPTIONS.replace_updated_files_list = []
3988 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3989 OPTIONS.replace_updated_files_list.append(care_map_path)
3990 else:
3991 ZipWrite(output_file, temp_care_map, arcname=care_map_path)