blob: d4da8b22cbcfef6e4df8f76ada34211b9a5be3b6 [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
Kelvin Zhang766eea72021-06-03 09:36:08 -0400279 if verbose is None:
280 verbose = OPTIONS.verbose
281
Tao Bao32fcdab2018-10-12 10:30:39 -0700282 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400283 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700285 return subprocess.Popen(args, **kwargs)
286
287
Tao Bao986ee862018-10-04 15:46:16 -0700288def RunAndCheckOutput(args, verbose=None, **kwargs):
289 """Runs the given command and returns the output.
290
291 Args:
292 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700293 verbose: Whether the commands should be shown. Default to the global
294 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700295 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
296 stdin, etc. stdout and stderr will default to subprocess.PIPE and
297 subprocess.STDOUT respectively unless caller specifies any of them.
298
299 Returns:
300 The output string.
301
302 Raises:
303 ExternalError: On non-zero exit from the command.
304 """
Tao Bao986ee862018-10-04 15:46:16 -0700305 proc = Run(args, verbose=verbose, **kwargs)
306 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800307 if output is None:
308 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700309 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400310 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700311 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700312 if proc.returncode != 0:
313 raise ExternalError(
314 "Failed to run command '{}' (exit code {}):\n{}".format(
315 args, proc.returncode, output))
316 return output
317
318
Tao Baoc765cca2018-01-31 17:32:40 -0800319def RoundUpTo4K(value):
320 rounded_up = value + 4095
321 return rounded_up - (rounded_up % 4096)
322
323
Ying Wang7e6d4e42010-12-13 16:25:36 -0800324def CloseInheritedPipes():
325 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
326 before doing other work."""
327 if platform.system() != "Darwin":
328 return
329 for d in range(3, 1025):
330 try:
331 stat = os.fstat(d)
332 if stat is not None:
333 pipebit = stat[0] & 0x1000
334 if pipebit != 0:
335 os.close(d)
336 except OSError:
337 pass
338
339
Tao Bao1c320f82019-10-04 23:25:12 -0700340class BuildInfo(object):
341 """A class that holds the information for a given build.
342
343 This class wraps up the property querying for a given source or target build.
344 It abstracts away the logic of handling OEM-specific properties, and caches
345 the commonly used properties such as fingerprint.
346
347 There are two types of info dicts: a) build-time info dict, which is generated
348 at build time (i.e. included in a target_files zip); b) OEM info dict that is
349 specified at package generation time (via command line argument
350 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
351 having "oem_fingerprint_properties" in build-time info dict), all the queries
352 would be answered based on build-time info dict only. Otherwise if using
353 OEM-specific properties, some of them will be calculated from two info dicts.
354
355 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800356 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700357
358 Attributes:
359 info_dict: The build-time info dict.
360 is_ab: Whether it's a build that uses A/B OTA.
361 oem_dicts: A list of OEM dicts.
362 oem_props: A list of OEM properties that should be read from OEM dicts; None
363 if the build doesn't use any OEM-specific property.
364 fingerprint: The fingerprint of the build, which would be calculated based
365 on OEM properties if applicable.
366 device: The device name, which could come from OEM dicts if applicable.
367 """
368
369 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
370 "ro.product.manufacturer", "ro.product.model",
371 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700372 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
373 "product", "odm", "vendor", "system_ext", "system"]
374 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
375 "product", "product_services", "odm", "vendor", "system"]
376 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700377
Tianjiefdda51d2021-05-05 14:46:35 -0700378 # The length of vbmeta digest to append to the fingerprint
379 _VBMETA_DIGEST_SIZE_USED = 8
380
381 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700382 """Initializes a BuildInfo instance with the given dicts.
383
384 Note that it only wraps up the given dicts, without making copies.
385
386 Arguments:
387 info_dict: The build-time info dict.
388 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
389 that it always uses the first dict to calculate the fingerprint or the
390 device name. The rest would be used for asserting OEM properties only
391 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700392 use_legacy_id: Use the legacy build id to construct the fingerprint. This
393 is used when we need a BuildInfo class, while the vbmeta digest is
394 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700395
396 Raises:
397 ValueError: On invalid inputs.
398 """
399 self.info_dict = info_dict
400 self.oem_dicts = oem_dicts
401
402 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700403 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700404
Hongguang Chend7c160f2020-05-03 21:24:26 -0700405 # Skip _oem_props if oem_dicts is None to use BuildInfo in
406 # sign_target_files_apks
407 if self.oem_dicts:
408 self._oem_props = info_dict.get("oem_fingerprint_properties")
409 else:
410 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700411
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 def check_fingerprint(fingerprint):
413 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
414 raise ValueError(
415 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
416 "3.2.2. Build Parameters.".format(fingerprint))
417
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800419 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800420 try:
421 fingerprint = self.CalculatePartitionFingerprint(partition)
422 check_fingerprint(fingerprint)
423 self._partition_fingerprints[partition] = fingerprint
424 except ExternalError:
425 continue
426 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800427 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800428 # need a fingerprint when creating the image.
429 self._partition_fingerprints[
430 "system_other"] = self._partition_fingerprints["system"]
431
Tao Bao1c320f82019-10-04 23:25:12 -0700432 # These two should be computed only after setting self._oem_props.
433 self._device = self.GetOemProperty("ro.product.device")
434 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700436
437 @property
438 def is_ab(self):
439 return self._is_ab
440
441 @property
442 def device(self):
443 return self._device
444
445 @property
446 def fingerprint(self):
447 return self._fingerprint
448
449 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400450 def is_vabc(self):
451 vendor_prop = self.info_dict.get("vendor.build.prop")
452 vabc_enabled = vendor_prop and \
453 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
454 return vabc_enabled
455
456 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700457 def oem_props(self):
458 return self._oem_props
459
460 def __getitem__(self, key):
461 return self.info_dict[key]
462
463 def __setitem__(self, key, value):
464 self.info_dict[key] = value
465
466 def get(self, key, default=None):
467 return self.info_dict.get(key, default)
468
469 def items(self):
470 return self.info_dict.items()
471
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000472 def _GetRawBuildProp(self, prop, partition):
473 prop_file = '{}.build.prop'.format(
474 partition) if partition else 'build.prop'
475 partition_props = self.info_dict.get(prop_file)
476 if not partition_props:
477 return None
478 return partition_props.GetProp(prop)
479
Daniel Normand5fe8622020-01-08 17:01:11 -0800480 def GetPartitionBuildProp(self, prop, partition):
481 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800482
483 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400484 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800485
Daniel Normand5fe8622020-01-08 17:01:11 -0800486 # If provided a partition for this property, only look within that
487 # partition's build.prop.
488 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800489 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800490 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800491 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000492
493 prop_val = self._GetRawBuildProp(prop, partition)
494 if prop_val is not None:
495 return prop_val
496 raise ExternalError("couldn't find %s in %s.build.prop" %
497 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800498
Tao Bao1c320f82019-10-04 23:25:12 -0700499 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800500 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700501 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
502 return self._ResolveRoProductBuildProp(prop)
503
Tianjiefdda51d2021-05-05 14:46:35 -0700504 if prop == "ro.build.id":
505 return self._GetBuildId()
506
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000507 prop_val = self._GetRawBuildProp(prop, None)
508 if prop_val is not None:
509 return prop_val
510
511 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700512
513 def _ResolveRoProductBuildProp(self, prop):
514 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000515 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700516 if prop_val:
517 return prop_val
518
Steven Laver8e2086e2020-04-27 16:26:31 -0700519 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 source_order_val = self._GetRawBuildProp(
521 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700522 if source_order_val:
523 source_order = source_order_val.split(",")
524 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700525 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700526
527 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700528 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700529 raise ExternalError(
530 "Invalid ro.product.property_source_order '{}'".format(source_order))
531
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000532 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700533 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000534 "ro.product", "ro.product.{}".format(source_partition), 1)
535 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700536 if prop_val:
537 return prop_val
538
539 raise ExternalError("couldn't resolve {}".format(prop))
540
Steven Laver8e2086e2020-04-27 16:26:31 -0700541 def _GetRoProductPropsDefaultSourceOrder(self):
542 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
543 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000544 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700545 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000546 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700547 if android_version == "10":
548 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
549 # NOTE: float() conversion of android_version will have rounding error.
550 # We are checking for "9" or less, and using "< 10" is well outside of
551 # possible floating point rounding.
552 try:
553 android_version_val = float(android_version)
554 except ValueError:
555 android_version_val = 0
556 if android_version_val < 10:
557 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
558 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
559
Tianjieb37c5be2020-10-15 21:27:10 -0700560 def _GetPlatformVersion(self):
561 version_sdk = self.GetBuildProp("ro.build.version.sdk")
562 # init code switches to version_release_or_codename (see b/158483506). After
563 # API finalization, release_or_codename will be the same as release. This
564 # is the best effort to support pre-S dev stage builds.
565 if int(version_sdk) >= 30:
566 try:
567 return self.GetBuildProp("ro.build.version.release_or_codename")
568 except ExternalError:
569 logger.warning('Failed to find ro.build.version.release_or_codename')
570
571 return self.GetBuildProp("ro.build.version.release")
572
Tianjiefdda51d2021-05-05 14:46:35 -0700573 def _GetBuildId(self):
574 build_id = self._GetRawBuildProp("ro.build.id", None)
575 if build_id:
576 return build_id
577
578 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
579 if not legacy_build_id:
580 raise ExternalError("Couldn't find build id in property file")
581
582 if self.use_legacy_id:
583 return legacy_build_id
584
585 # Append the top 8 chars of vbmeta digest to the existing build id. The
586 # logic needs to match the one in init, so that OTA can deliver correctly.
587 avb_enable = self.info_dict.get("avb_enable") == "true"
588 if not avb_enable:
589 raise ExternalError("AVB isn't enabled when using legacy build id")
590
591 vbmeta_digest = self.info_dict.get("vbmeta_digest")
592 if not vbmeta_digest:
593 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
594 " id")
595 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
596 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
597
598 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
599 return legacy_build_id + '.' + digest_prefix
600
Tianjieb37c5be2020-10-15 21:27:10 -0700601 def _GetPartitionPlatformVersion(self, partition):
602 try:
603 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
604 partition)
605 except ExternalError:
606 return self.GetPartitionBuildProp("ro.build.version.release",
607 partition)
608
Tao Bao1c320f82019-10-04 23:25:12 -0700609 def GetOemProperty(self, key):
610 if self.oem_props is not None and key in self.oem_props:
611 return self.oem_dicts[0][key]
612 return self.GetBuildProp(key)
613
Daniel Normand5fe8622020-01-08 17:01:11 -0800614 def GetPartitionFingerprint(self, partition):
615 return self._partition_fingerprints.get(partition, None)
616
617 def CalculatePartitionFingerprint(self, partition):
618 try:
619 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
620 except ExternalError:
621 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
622 self.GetPartitionBuildProp("ro.product.brand", partition),
623 self.GetPartitionBuildProp("ro.product.name", partition),
624 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700625 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800626 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400627 self.GetPartitionBuildProp(
628 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800629 self.GetPartitionBuildProp("ro.build.type", partition),
630 self.GetPartitionBuildProp("ro.build.tags", partition))
631
Tao Bao1c320f82019-10-04 23:25:12 -0700632 def CalculateFingerprint(self):
633 if self.oem_props is None:
634 try:
635 return self.GetBuildProp("ro.build.fingerprint")
636 except ExternalError:
637 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
638 self.GetBuildProp("ro.product.brand"),
639 self.GetBuildProp("ro.product.name"),
640 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700641 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700642 self.GetBuildProp("ro.build.id"),
643 self.GetBuildProp("ro.build.version.incremental"),
644 self.GetBuildProp("ro.build.type"),
645 self.GetBuildProp("ro.build.tags"))
646 return "%s/%s/%s:%s" % (
647 self.GetOemProperty("ro.product.brand"),
648 self.GetOemProperty("ro.product.name"),
649 self.GetOemProperty("ro.product.device"),
650 self.GetBuildProp("ro.build.thumbprint"))
651
652 def WriteMountOemScript(self, script):
653 assert self.oem_props is not None
654 recovery_mount_options = self.info_dict.get("recovery_mount_options")
655 script.Mount("/oem", recovery_mount_options)
656
657 def WriteDeviceAssertions(self, script, oem_no_mount):
658 # Read the property directly if not using OEM properties.
659 if not self.oem_props:
660 script.AssertDevice(self.device)
661 return
662
663 # Otherwise assert OEM properties.
664 if not self.oem_dicts:
665 raise ExternalError(
666 "No OEM file provided to answer expected assertions")
667
668 for prop in self.oem_props.split():
669 values = []
670 for oem_dict in self.oem_dicts:
671 if prop in oem_dict:
672 values.append(oem_dict[prop])
673 if not values:
674 raise ExternalError(
675 "The OEM file is missing the property %s" % (prop,))
676 script.AssertOemProperty(prop, values, oem_no_mount)
677
678
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000679def ReadFromInputFile(input_file, fn):
680 """Reads the contents of fn from input zipfile or directory."""
681 if isinstance(input_file, zipfile.ZipFile):
682 return input_file.read(fn).decode()
683 else:
684 path = os.path.join(input_file, *fn.split("/"))
685 try:
686 with open(path) as f:
687 return f.read()
688 except IOError as e:
689 if e.errno == errno.ENOENT:
690 raise KeyError(fn)
691
692
Yifan Hong10482a22021-01-07 14:38:41 -0800693def ExtractFromInputFile(input_file, fn):
694 """Extracts the contents of fn from input zipfile or directory into a file."""
695 if isinstance(input_file, zipfile.ZipFile):
696 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500697 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800698 f.write(input_file.read(fn))
699 return tmp_file
700 else:
701 file = os.path.join(input_file, *fn.split("/"))
702 if not os.path.exists(file):
703 raise KeyError(fn)
704 return file
705
Kelvin Zhang563750f2021-04-28 12:46:17 -0400706
jiajia tangf3f842b2021-03-17 21:49:44 +0800707class RamdiskFormat(object):
708 LZ4 = 1
709 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800710
Kelvin Zhang563750f2021-04-28 12:46:17 -0400711
jiajia tang836f76b2021-04-02 14:48:26 +0800712def _GetRamdiskFormat(info_dict):
713 if info_dict.get('lz4_ramdisks') == 'true':
714 ramdisk_format = RamdiskFormat.LZ4
715 else:
716 ramdisk_format = RamdiskFormat.GZ
717 return ramdisk_format
718
Kelvin Zhang563750f2021-04-28 12:46:17 -0400719
Tao Bao410ad8b2018-08-24 12:08:38 -0700720def LoadInfoDict(input_file, repacking=False):
721 """Loads the key/value pairs from the given input target_files.
722
Tianjiea85bdf02020-07-29 11:56:19 -0700723 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700724 checks and returns the parsed key/value pairs for to the given build. It's
725 usually called early when working on input target_files files, e.g. when
726 generating OTAs, or signing builds. Note that the function may be called
727 against an old target_files file (i.e. from past dessert releases). So the
728 property parsing needs to be backward compatible.
729
730 In a `META/misc_info.txt`, a few properties are stored as links to the files
731 in the PRODUCT_OUT directory. It works fine with the build system. However,
732 they are no longer available when (re)generating images from target_files zip.
733 When `repacking` is True, redirect these properties to the actual files in the
734 unzipped directory.
735
736 Args:
737 input_file: The input target_files file, which could be an open
738 zipfile.ZipFile instance, or a str for the dir that contains the files
739 unzipped from a target_files file.
740 repacking: Whether it's trying repack an target_files file after loading the
741 info dict (default: False). If so, it will rewrite a few loaded
742 properties (e.g. selinux_fc, root_dir) to point to the actual files in
743 target_files file. When doing repacking, `input_file` must be a dir.
744
745 Returns:
746 A dict that contains the parsed key/value pairs.
747
748 Raises:
749 AssertionError: On invalid input arguments.
750 ValueError: On malformed input values.
751 """
752 if repacking:
753 assert isinstance(input_file, str), \
754 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700755
Doug Zongkerc9253822014-02-04 12:17:58 -0800756 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000757 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800758
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700759 try:
Michael Runge6e836112014-04-15 17:40:21 -0700760 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700761 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700762 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700763
Tao Bao410ad8b2018-08-24 12:08:38 -0700764 if "recovery_api_version" not in d:
765 raise ValueError("Failed to find 'recovery_api_version'")
766 if "fstab_version" not in d:
767 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800768
Tao Bao410ad8b2018-08-24 12:08:38 -0700769 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700770 # "selinux_fc" properties should point to the file_contexts files
771 # (file_contexts.bin) under META/.
772 for key in d:
773 if key.endswith("selinux_fc"):
774 fc_basename = os.path.basename(d[key])
775 fc_config = os.path.join(input_file, "META", fc_basename)
776 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700777
Daniel Norman72c626f2019-05-13 15:58:14 -0700778 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700779
Tom Cherryd14b8952018-08-09 14:26:00 -0700780 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700781 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700782 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700783 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700784
David Anderson0ec64ac2019-12-06 12:21:18 -0800785 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700786 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700787 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800788 key_name = part_name + "_base_fs_file"
789 if key_name not in d:
790 continue
791 basename = os.path.basename(d[key_name])
792 base_fs_file = os.path.join(input_file, "META", basename)
793 if os.path.exists(base_fs_file):
794 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700795 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700796 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800797 "Failed to find %s base fs file: %s", part_name, base_fs_file)
798 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700799
Doug Zongker37974732010-09-16 17:44:38 -0700800 def makeint(key):
801 if key in d:
802 d[key] = int(d[key], 0)
803
804 makeint("recovery_api_version")
805 makeint("blocksize")
806 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700807 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700808 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700809 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700810 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800811 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700812
Steve Muckle903a1ca2020-05-07 17:32:10 -0700813 boot_images = "boot.img"
814 if "boot_images" in d:
815 boot_images = d["boot_images"]
816 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400817 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700818
Tao Bao765668f2019-10-04 22:03:00 -0700819 # Load recovery fstab if applicable.
820 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800821 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800822
Tianjie Xu861f4132018-09-12 11:49:33 -0700823 # Tries to load the build props for all partitions with care_map, including
824 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800825 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800826 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000827 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800828 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700829 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800830
Tao Bao3ed35d32019-10-07 20:48:48 -0700831 # Set up the salt (based on fingerprint) that will be used when adding AVB
832 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800833 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700834 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800835 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800836 fingerprint = build_info.GetPartitionFingerprint(partition)
837 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400838 d["avb_{}_salt".format(partition)] = sha256(
839 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700840
841 # Set the vbmeta digest if exists
842 try:
843 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
844 except KeyError:
845 pass
846
Kelvin Zhang39aea442020-08-17 11:04:25 -0400847 try:
848 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
849 except KeyError:
850 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700851 return d
852
Tao Baod1de6f32017-03-01 16:38:48 -0800853
Daniel Norman4cc9df62019-07-18 10:11:07 -0700854def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900855 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700856 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900857
Daniel Norman4cc9df62019-07-18 10:11:07 -0700858
859def LoadDictionaryFromFile(file_path):
860 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900861 return LoadDictionaryFromLines(lines)
862
863
Michael Runge6e836112014-04-15 17:40:21 -0700864def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700865 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700866 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700867 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700868 if not line or line.startswith("#"):
869 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700870 if "=" in line:
871 name, value = line.split("=", 1)
872 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700873 return d
874
Tao Baod1de6f32017-03-01 16:38:48 -0800875
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000876class PartitionBuildProps(object):
877 """The class holds the build prop of a particular partition.
878
879 This class loads the build.prop and holds the build properties for a given
880 partition. It also partially recognizes the 'import' statement in the
881 build.prop; and calculates alternative values of some specific build
882 properties during runtime.
883
884 Attributes:
885 input_file: a zipped target-file or an unzipped target-file directory.
886 partition: name of the partition.
887 props_allow_override: a list of build properties to search for the
888 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000889 build_props: a dict of build properties for the given partition.
890 prop_overrides: a set of props that are overridden by import.
891 placeholder_values: A dict of runtime variables' values to replace the
892 placeholders in the build.prop file. We expect exactly one value for
893 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800894 ramdisk_format: If name is "boot", the format of ramdisk inside the
895 boot image. Otherwise, its value is ignored.
896 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000897 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400898
Tianjie Xu9afb2212020-05-10 21:48:15 +0000899 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000900 self.input_file = input_file
901 self.partition = name
902 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000903 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000904 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000905 self.prop_overrides = set()
906 self.placeholder_values = {}
907 if placeholder_values:
908 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000909
910 @staticmethod
911 def FromDictionary(name, build_props):
912 """Constructs an instance from a build prop dictionary."""
913
914 props = PartitionBuildProps("unknown", name)
915 props.build_props = build_props.copy()
916 return props
917
918 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800919 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000920 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800921
922 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400923 data = PartitionBuildProps._ReadBootPropFile(
924 input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800925 else:
926 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
927
928 props = PartitionBuildProps(input_file, name, placeholder_values)
929 props._LoadBuildProp(data)
930 return props
931
932 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800933 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800934 """
935 Read build.prop for boot image from input_file.
936 Return empty string if not found.
937 """
938 try:
939 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
940 except KeyError:
941 logger.warning('Failed to read IMAGES/boot.img')
942 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800943 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800944 if prop_file is None:
945 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500946 with open(prop_file, "r") as f:
947 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800948
949 @staticmethod
950 def _ReadPartitionPropFile(input_file, name):
951 """
952 Read build.prop for name from input_file.
953 Return empty string if not found.
954 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000955 data = ''
956 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
957 '{}/build.prop'.format(name.upper())]:
958 try:
959 data = ReadFromInputFile(input_file, prop_file)
960 break
961 except KeyError:
962 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800963 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000964
Yifan Hong125d0b62020-09-24 17:07:03 -0700965 @staticmethod
966 def FromBuildPropFile(name, build_prop_file):
967 """Constructs an instance from a build prop file."""
968
969 props = PartitionBuildProps("unknown", name)
970 with open(build_prop_file) as f:
971 props._LoadBuildProp(f.read())
972 return props
973
Tianjie Xu9afb2212020-05-10 21:48:15 +0000974 def _LoadBuildProp(self, data):
975 for line in data.split('\n'):
976 line = line.strip()
977 if not line or line.startswith("#"):
978 continue
979 if line.startswith("import"):
980 overrides = self._ImportParser(line)
981 duplicates = self.prop_overrides.intersection(overrides.keys())
982 if duplicates:
983 raise ValueError('prop {} is overridden multiple times'.format(
984 ','.join(duplicates)))
985 self.prop_overrides = self.prop_overrides.union(overrides.keys())
986 self.build_props.update(overrides)
987 elif "=" in line:
988 name, value = line.split("=", 1)
989 if name in self.prop_overrides:
990 raise ValueError('prop {} is set again after overridden by import '
991 'statement'.format(name))
992 self.build_props[name] = value
993
994 def _ImportParser(self, line):
995 """Parses the build prop in a given import statement."""
996
997 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400998 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000999 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001000
1001 if len(tokens) == 3:
1002 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1003 return {}
1004
Tianjie Xu9afb2212020-05-10 21:48:15 +00001005 import_path = tokens[1]
1006 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1007 raise ValueError('Unrecognized import path {}'.format(line))
1008
1009 # We only recognize a subset of import statement that the init process
1010 # supports. And we can loose the restriction based on how the dynamic
1011 # fingerprint is used in practice. The placeholder format should be
1012 # ${placeholder}, and its value should be provided by the caller through
1013 # the placeholder_values.
1014 for prop, value in self.placeholder_values.items():
1015 prop_place_holder = '${{{}}}'.format(prop)
1016 if prop_place_holder in import_path:
1017 import_path = import_path.replace(prop_place_holder, value)
1018 if '$' in import_path:
1019 logger.info('Unresolved place holder in import path %s', import_path)
1020 return {}
1021
1022 import_path = import_path.replace('/{}'.format(self.partition),
1023 self.partition.upper())
1024 logger.info('Parsing build props override from %s', import_path)
1025
1026 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1027 d = LoadDictionaryFromLines(lines)
1028 return {key: val for key, val in d.items()
1029 if key in self.props_allow_override}
1030
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001031 def GetProp(self, prop):
1032 return self.build_props.get(prop)
1033
1034
Tianjie Xucfa86222016-03-07 16:31:19 -08001035def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1036 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001037 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001038 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001039 self.mount_point = mount_point
1040 self.fs_type = fs_type
1041 self.device = device
1042 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001043 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001044 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001045
1046 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001047 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001048 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001049 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001050 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001051
Tao Baod1de6f32017-03-01 16:38:48 -08001052 assert fstab_version == 2
1053
1054 d = {}
1055 for line in data.split("\n"):
1056 line = line.strip()
1057 if not line or line.startswith("#"):
1058 continue
1059
1060 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1061 pieces = line.split()
1062 if len(pieces) != 5:
1063 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1064
1065 # Ignore entries that are managed by vold.
1066 options = pieces[4]
1067 if "voldmanaged=" in options:
1068 continue
1069
1070 # It's a good line, parse it.
1071 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001072 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001073 options = options.split(",")
1074 for i in options:
1075 if i.startswith("length="):
1076 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001077 elif i == "slotselect":
1078 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001079 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001080 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001081 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001082
Tao Baod1de6f32017-03-01 16:38:48 -08001083 mount_flags = pieces[3]
1084 # Honor the SELinux context if present.
1085 context = None
1086 for i in mount_flags.split(","):
1087 if i.startswith("context="):
1088 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001089
Tao Baod1de6f32017-03-01 16:38:48 -08001090 mount_point = pieces[1]
1091 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001092 device=pieces[0], length=length, context=context,
1093 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001094
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001095 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001096 # system. Other areas assume system is always at "/system" so point /system
1097 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001098 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001099 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001100 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001101 return d
1102
1103
Tao Bao765668f2019-10-04 22:03:00 -07001104def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1105 """Finds the path to recovery fstab and loads its contents."""
1106 # recovery fstab is only meaningful when installing an update via recovery
1107 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001108 if info_dict.get('ab_update') == 'true' and \
1109 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001110 return None
1111
1112 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1113 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1114 # cases, since it may load the info_dict from an old build (e.g. when
1115 # generating incremental OTAs from that build).
1116 system_root_image = info_dict.get('system_root_image') == 'true'
1117 if info_dict.get('no_recovery') != 'true':
1118 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1119 if isinstance(input_file, zipfile.ZipFile):
1120 if recovery_fstab_path not in input_file.namelist():
1121 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1122 else:
1123 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1124 if not os.path.exists(path):
1125 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1126 return LoadRecoveryFSTab(
1127 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1128 system_root_image)
1129
1130 if info_dict.get('recovery_as_boot') == 'true':
1131 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1132 if isinstance(input_file, zipfile.ZipFile):
1133 if recovery_fstab_path not in input_file.namelist():
1134 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1135 else:
1136 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1137 if not os.path.exists(path):
1138 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1139 return LoadRecoveryFSTab(
1140 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1141 system_root_image)
1142
1143 return None
1144
1145
Doug Zongker37974732010-09-16 17:44:38 -07001146def DumpInfoDict(d):
1147 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001148 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001149
Dan Albert8b72aef2015-03-23 19:13:21 -07001150
Daniel Norman55417142019-11-25 16:04:36 -08001151def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001152 """Merges dynamic partition info variables.
1153
1154 Args:
1155 framework_dict: The dictionary of dynamic partition info variables from the
1156 partial framework target files.
1157 vendor_dict: The dictionary of dynamic partition info variables from the
1158 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001159
1160 Returns:
1161 The merged dynamic partition info dictionary.
1162 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001163
1164 def uniq_concat(a, b):
1165 combined = set(a.split(" "))
1166 combined.update(set(b.split(" ")))
1167 combined = [item.strip() for item in combined if item.strip()]
1168 return " ".join(sorted(combined))
1169
1170 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001171 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001172 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1173
1174 merged_dict = {"use_dynamic_partitions": "true"}
1175
1176 merged_dict["dynamic_partition_list"] = uniq_concat(
1177 framework_dict.get("dynamic_partition_list", ""),
1178 vendor_dict.get("dynamic_partition_list", ""))
1179
1180 # Super block devices are defined by the vendor dict.
1181 if "super_block_devices" in vendor_dict:
1182 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1183 for block_device in merged_dict["super_block_devices"].split(" "):
1184 key = "super_%s_device_size" % block_device
1185 if key not in vendor_dict:
1186 raise ValueError("Vendor dict does not contain required key %s." % key)
1187 merged_dict[key] = vendor_dict[key]
1188
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001189 # Partition groups and group sizes are defined by the vendor dict because
1190 # these values may vary for each board that uses a shared system image.
1191 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001192 for partition_group in merged_dict["super_partition_groups"].split(" "):
1193 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001194 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001195 if key not in vendor_dict:
1196 raise ValueError("Vendor dict does not contain required key %s." % key)
1197 merged_dict[key] = vendor_dict[key]
1198
1199 # Set the partition group's partition list using a concatenation of the
1200 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001201 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001202 merged_dict[key] = uniq_concat(
1203 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301204
Daniel Normanb0c75912020-09-24 14:30:21 -07001205 # Various other flags should be copied from the vendor dict, if defined.
1206 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1207 "super_metadata_device", "super_partition_error_limit",
1208 "super_partition_size"):
1209 if key in vendor_dict.keys():
1210 merged_dict[key] = vendor_dict[key]
1211
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001212 return merged_dict
1213
1214
Daniel Norman21c34f72020-11-11 17:25:50 -08001215def PartitionMapFromTargetFiles(target_files_dir):
1216 """Builds a map from partition -> path within an extracted target files directory."""
1217 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1218 possible_subdirs = {
1219 "system": ["SYSTEM"],
1220 "vendor": ["VENDOR", "SYSTEM/vendor"],
1221 "product": ["PRODUCT", "SYSTEM/product"],
1222 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1223 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1224 "vendor_dlkm": [
1225 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1226 ],
1227 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1228 }
1229 partition_map = {}
1230 for partition, subdirs in possible_subdirs.items():
1231 for subdir in subdirs:
1232 if os.path.exists(os.path.join(target_files_dir, subdir)):
1233 partition_map[partition] = subdir
1234 break
1235 return partition_map
1236
1237
Daniel Normand3351562020-10-29 12:33:11 -07001238def SharedUidPartitionViolations(uid_dict, partition_groups):
1239 """Checks for APK sharedUserIds that cross partition group boundaries.
1240
1241 This uses a single or merged build's shareduid_violation_modules.json
1242 output file, as generated by find_shareduid_violation.py or
1243 core/tasks/find-shareduid-violation.mk.
1244
1245 An error is defined as a sharedUserId that is found in a set of partitions
1246 that span more than one partition group.
1247
1248 Args:
1249 uid_dict: A dictionary created by using the standard json module to read a
1250 complete shareduid_violation_modules.json file.
1251 partition_groups: A list of groups, where each group is a list of
1252 partitions.
1253
1254 Returns:
1255 A list of error messages.
1256 """
1257 errors = []
1258 for uid, partitions in uid_dict.items():
1259 found_in_groups = [
1260 group for group in partition_groups
1261 if set(partitions.keys()) & set(group)
1262 ]
1263 if len(found_in_groups) > 1:
1264 errors.append(
1265 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1266 % (uid, ",".join(sorted(partitions.keys()))))
1267 return errors
1268
1269
Daniel Norman21c34f72020-11-11 17:25:50 -08001270def RunHostInitVerifier(product_out, partition_map):
1271 """Runs host_init_verifier on the init rc files within partitions.
1272
1273 host_init_verifier searches the etc/init path within each partition.
1274
1275 Args:
1276 product_out: PRODUCT_OUT directory, containing partition directories.
1277 partition_map: A map of partition name -> relative path within product_out.
1278 """
1279 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1280 cmd = ["host_init_verifier"]
1281 for partition, path in partition_map.items():
1282 if partition not in allowed_partitions:
1283 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1284 partition)
1285 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1286 # Add --property-contexts if the file exists on the partition.
1287 property_contexts = "%s_property_contexts" % (
1288 "plat" if partition == "system" else partition)
1289 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1290 property_contexts)
1291 if os.path.exists(property_contexts_path):
1292 cmd.append("--property-contexts=%s" % property_contexts_path)
1293 # Add the passwd file if the file exists on the partition.
1294 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1295 if os.path.exists(passwd_path):
1296 cmd.extend(["-p", passwd_path])
1297 return RunAndCheckOutput(cmd)
1298
1299
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001300def AppendAVBSigningArgs(cmd, partition):
1301 """Append signing arguments for avbtool."""
1302 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1303 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001304 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1305 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1306 if os.path.exists(new_key_path):
1307 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001308 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1309 if key_path and algorithm:
1310 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001311 avb_salt = OPTIONS.info_dict.get("avb_salt")
1312 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001313 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001314 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001315
1316
Tao Bao765668f2019-10-04 22:03:00 -07001317def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001318 """Returns the VBMeta arguments for partition.
1319
1320 It sets up the VBMeta argument by including the partition descriptor from the
1321 given 'image', or by configuring the partition as a chained partition.
1322
1323 Args:
1324 partition: The name of the partition (e.g. "system").
1325 image: The path to the partition image.
1326 info_dict: A dict returned by common.LoadInfoDict(). Will use
1327 OPTIONS.info_dict if None has been given.
1328
1329 Returns:
1330 A list of VBMeta arguments.
1331 """
1332 if info_dict is None:
1333 info_dict = OPTIONS.info_dict
1334
1335 # Check if chain partition is used.
1336 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001337 if not key_path:
1338 return ["--include_descriptors_from_image", image]
1339
1340 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1341 # into vbmeta.img. The recovery image will be configured on an independent
1342 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1343 # See details at
1344 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001345 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001346 return []
1347
1348 # Otherwise chain the partition into vbmeta.
1349 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1350 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001351
1352
Tao Bao02a08592018-07-22 12:40:45 -07001353def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1354 """Constructs and returns the arg to build or verify a chained partition.
1355
1356 Args:
1357 partition: The partition name.
1358 info_dict: The info dict to look up the key info and rollback index
1359 location.
1360 key: The key to be used for building or verifying the partition. Defaults to
1361 the key listed in info_dict.
1362
1363 Returns:
1364 A string of form "partition:rollback_index_location:key" that can be used to
1365 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001366 """
1367 if key is None:
1368 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001369 if key and not os.path.exists(key) and OPTIONS.search_path:
1370 new_key_path = os.path.join(OPTIONS.search_path, key)
1371 if os.path.exists(new_key_path):
1372 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001373 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001374 rollback_index_location = info_dict[
1375 "avb_" + partition + "_rollback_index_location"]
1376 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1377
1378
Tianjie20dd8f22020-04-19 15:51:16 -07001379def ConstructAftlMakeImageCommands(output_image):
1380 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001381
1382 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001383 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001384 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1385 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1386 'No AFTL manufacturer key provided.'
1387
1388 vbmeta_image = MakeTempFile()
1389 os.rename(output_image, vbmeta_image)
Tianjiefdda51d2021-05-05 14:46:35 -07001390 build_info = BuildInfo(OPTIONS.info_dict, use_legacy_id=True)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001391 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001392 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001393 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001394 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001395 "--vbmeta_image_path", vbmeta_image,
1396 "--output", output_image,
1397 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001398 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001399 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1400 "--algorithm", "SHA256_RSA4096",
1401 "--padding", "4096"]
1402 if OPTIONS.aftl_signer_helper:
1403 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001404 return aftl_cmd
1405
1406
1407def AddAftlInclusionProof(output_image):
1408 """Appends the aftl inclusion proof to the vbmeta image."""
1409
1410 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001411 RunAndCheckOutput(aftl_cmd)
1412
1413 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1414 output_image, '--transparency_log_pub_keys',
1415 OPTIONS.aftl_key_path]
1416 RunAndCheckOutput(verify_cmd)
1417
1418
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001419def AppendGkiSigningArgs(cmd):
1420 """Append GKI signing arguments for mkbootimg."""
1421 # e.g., --gki_signing_key path/to/signing_key
1422 # --gki_signing_algorithm SHA256_RSA4096"
1423
1424 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1425 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1426 if not key_path:
1427 return
1428
1429 if not os.path.exists(key_path) and OPTIONS.search_path:
1430 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1431 if os.path.exists(new_key_path):
1432 key_path = new_key_path
1433
1434 # Checks key_path exists, before appending --gki_signing_* args.
1435 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001436 raise ExternalError(
1437 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001438
1439 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1440 if key_path and algorithm:
1441 cmd.extend(["--gki_signing_key", key_path,
1442 "--gki_signing_algorithm", algorithm])
1443
1444 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1445 if signature_args:
1446 cmd.extend(["--gki_signing_signature_args", signature_args])
1447
1448
Daniel Norman276f0622019-07-26 14:13:51 -07001449def BuildVBMeta(image_path, partitions, name, needed_partitions):
1450 """Creates a VBMeta image.
1451
1452 It generates the requested VBMeta image. The requested image could be for
1453 top-level or chained VBMeta image, which is determined based on the name.
1454
1455 Args:
1456 image_path: The output path for the new VBMeta image.
1457 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001458 values. Only valid partition names are accepted, as partitions listed
1459 in common.AVB_PARTITIONS and custom partitions listed in
1460 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001461 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1462 needed_partitions: Partitions whose descriptors should be included into the
1463 generated VBMeta image.
1464
1465 Raises:
1466 AssertionError: On invalid input args.
1467 """
1468 avbtool = OPTIONS.info_dict["avb_avbtool"]
1469 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1470 AppendAVBSigningArgs(cmd, name)
1471
Hongguang Chenf23364d2020-04-27 18:36:36 -07001472 custom_partitions = OPTIONS.info_dict.get(
1473 "avb_custom_images_partition_list", "").strip().split()
1474
Daniel Norman276f0622019-07-26 14:13:51 -07001475 for partition, path in partitions.items():
1476 if partition not in needed_partitions:
1477 continue
1478 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001479 partition in AVB_VBMETA_PARTITIONS or
1480 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001481 'Unknown partition: {}'.format(partition)
1482 assert os.path.exists(path), \
1483 'Failed to find {} for {}'.format(path, partition)
1484 cmd.extend(GetAvbPartitionArg(partition, path))
1485
1486 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1487 if args and args.strip():
1488 split_args = shlex.split(args)
1489 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001490 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001491 # as a path relative to source tree, which may not be available at the
1492 # same location when running this script (we have the input target_files
1493 # zip only). For such cases, we additionally scan other locations (e.g.
1494 # IMAGES/, RADIO/, etc) before bailing out.
1495 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001496 chained_image = split_args[index + 1]
1497 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001498 continue
1499 found = False
1500 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1501 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001502 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001503 if os.path.exists(alt_path):
1504 split_args[index + 1] = alt_path
1505 found = True
1506 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001507 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001508 cmd.extend(split_args)
1509
1510 RunAndCheckOutput(cmd)
1511
Tianjie Xueaed60c2020-03-12 00:33:28 -07001512 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001513 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001514 AddAftlInclusionProof(image_path)
1515
Daniel Norman276f0622019-07-26 14:13:51 -07001516
jiajia tang836f76b2021-04-02 14:48:26 +08001517def _MakeRamdisk(sourcedir, fs_config_file=None,
1518 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001519 ramdisk_img = tempfile.NamedTemporaryFile()
1520
1521 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1522 cmd = ["mkbootfs", "-f", fs_config_file,
1523 os.path.join(sourcedir, "RAMDISK")]
1524 else:
1525 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1526 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001527 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001528 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001529 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001530 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001531 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001532 else:
1533 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001534
1535 p2.wait()
1536 p1.wait()
1537 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001538 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001539
1540 return ramdisk_img
1541
1542
Steve Muckle9793cf62020-04-08 18:27:00 -07001543def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001544 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001545 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001546
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001547 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001548 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1549 we are building a two-step special image (i.e. building a recovery image to
1550 be loaded into /boot in two-step OTAs).
1551
1552 Return the image data, or None if sourcedir does not appear to contains files
1553 for building the requested image.
1554 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001555
Yifan Hong63c5ca12020-10-08 11:54:02 -07001556 if info_dict is None:
1557 info_dict = OPTIONS.info_dict
1558
Steve Muckle9793cf62020-04-08 18:27:00 -07001559 # "boot" or "recovery", without extension.
1560 partition_name = os.path.basename(sourcedir).lower()
1561
Yifan Hong63c5ca12020-10-08 11:54:02 -07001562 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001563 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001564 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1565 logger.info("Excluded kernel binary from recovery image.")
1566 else:
1567 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001568 else:
1569 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001570 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001571 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001572 return None
1573
1574 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001575 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001576
Doug Zongkereef39442009-04-02 12:14:19 -07001577 img = tempfile.NamedTemporaryFile()
1578
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001579 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001580 ramdisk_format = _GetRamdiskFormat(info_dict)
1581 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1582 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001583
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001584 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1585 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1586
Yifan Hong63c5ca12020-10-08 11:54:02 -07001587 cmd = [mkbootimg]
1588 if kernel:
1589 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001590
Benoit Fradina45a8682014-07-14 21:00:43 +02001591 fn = os.path.join(sourcedir, "second")
1592 if os.access(fn, os.F_OK):
1593 cmd.append("--second")
1594 cmd.append(fn)
1595
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001596 fn = os.path.join(sourcedir, "dtb")
1597 if os.access(fn, os.F_OK):
1598 cmd.append("--dtb")
1599 cmd.append(fn)
1600
Doug Zongker171f1cd2009-06-15 22:36:37 -07001601 fn = os.path.join(sourcedir, "cmdline")
1602 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001603 cmd.append("--cmdline")
1604 cmd.append(open(fn).read().rstrip("\n"))
1605
1606 fn = os.path.join(sourcedir, "base")
1607 if os.access(fn, os.F_OK):
1608 cmd.append("--base")
1609 cmd.append(open(fn).read().rstrip("\n"))
1610
Ying Wang4de6b5b2010-08-25 14:29:34 -07001611 fn = os.path.join(sourcedir, "pagesize")
1612 if os.access(fn, os.F_OK):
1613 cmd.append("--pagesize")
1614 cmd.append(open(fn).read().rstrip("\n"))
1615
Steve Mucklef84668e2020-03-16 19:13:46 -07001616 if partition_name == "recovery":
1617 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301618 if not args:
1619 # Fall back to "mkbootimg_args" for recovery image
1620 # in case "recovery_mkbootimg_args" is not set.
1621 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001622 else:
1623 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001624 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001625 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001626
Tao Bao76def242017-11-21 09:25:31 -08001627 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001628 if args and args.strip():
1629 cmd.extend(shlex.split(args))
1630
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001631 if has_ramdisk:
1632 cmd.extend(["--ramdisk", ramdisk_img.name])
1633
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001634 AppendGkiSigningArgs(cmd)
1635
Tao Baod95e9fd2015-03-29 23:07:41 -07001636 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001637 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001638 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001639 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001640 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001641 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001642
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001643 if partition_name == "recovery":
1644 if info_dict.get("include_recovery_dtbo") == "true":
1645 fn = os.path.join(sourcedir, "recovery_dtbo")
1646 cmd.extend(["--recovery_dtbo", fn])
1647 if info_dict.get("include_recovery_acpio") == "true":
1648 fn = os.path.join(sourcedir, "recovery_acpio")
1649 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001650
Tao Bao986ee862018-10-04 15:46:16 -07001651 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001652
Tao Bao76def242017-11-21 09:25:31 -08001653 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001654 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001655 # Hard-code the path as "/boot" for two-step special recovery image (which
1656 # will be loaded into /boot during the two-step OTA).
1657 if two_step_image:
1658 path = "/boot"
1659 else:
Tao Baobf70c312017-07-11 17:27:55 -07001660 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001661 cmd = [OPTIONS.boot_signer_path]
1662 cmd.extend(OPTIONS.boot_signer_args)
1663 cmd.extend([path, img.name,
1664 info_dict["verity_key"] + ".pk8",
1665 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001666 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001667
Tao Baod95e9fd2015-03-29 23:07:41 -07001668 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001669 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001670 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001671 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001672 # We have switched from the prebuilt futility binary to using the tool
1673 # (futility-host) built from the source. Override the setting in the old
1674 # TF.zip.
1675 futility = info_dict["futility"]
1676 if futility.startswith("prebuilts/"):
1677 futility = "futility-host"
1678 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001679 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001680 info_dict["vboot_key"] + ".vbprivk",
1681 info_dict["vboot_subkey"] + ".vbprivk",
1682 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001683 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001684 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001685
Tao Baof3282b42015-04-01 11:21:55 -07001686 # Clean up the temp files.
1687 img_unsigned.close()
1688 img_keyblock.close()
1689
David Zeuthen8fecb282017-12-01 16:24:01 -05001690 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001691 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001692 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001693 if partition_name == "recovery":
1694 part_size = info_dict["recovery_size"]
1695 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001696 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001697 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001698 "--partition_size", str(part_size), "--partition_name",
1699 partition_name]
1700 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001701 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001702 if args and args.strip():
1703 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001704 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001705
1706 img.seek(os.SEEK_SET, 0)
1707 data = img.read()
1708
1709 if has_ramdisk:
1710 ramdisk_img.close()
1711 img.close()
1712
1713 return data
1714
1715
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001716def _SignBootableImage(image_path, prebuilt_name, partition_name,
1717 info_dict=None):
1718 """Performs AVB signing for a prebuilt boot.img.
1719
1720 Args:
1721 image_path: The full path of the image, e.g., /path/to/boot.img.
1722 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1723 boot-5.10.img, recovery.img.
1724 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1725 info_dict: The information dict read from misc_info.txt.
1726 """
1727 if info_dict is None:
1728 info_dict = OPTIONS.info_dict
1729
1730 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1731 if info_dict.get("avb_enable") == "true":
1732 avbtool = info_dict["avb_avbtool"]
1733 if partition_name == "recovery":
1734 part_size = info_dict["recovery_size"]
1735 else:
1736 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1737
1738 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1739 "--partition_size", str(part_size), "--partition_name",
1740 partition_name]
1741 AppendAVBSigningArgs(cmd, partition_name)
1742 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1743 if args and args.strip():
1744 cmd.extend(shlex.split(args))
1745 RunAndCheckOutput(cmd)
1746
1747
Doug Zongkerd5131602012-08-02 14:46:42 -07001748def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001749 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001750 """Return a File object with the desired bootable image.
1751
1752 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1753 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1754 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001755
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001756 if info_dict is None:
1757 info_dict = OPTIONS.info_dict
1758
Doug Zongker55d93282011-01-25 17:03:34 -08001759 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1760 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001761 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001762 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001763
1764 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1765 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001766 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001767 return File.FromLocalFile(name, prebuilt_path)
1768
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001769 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1770 if os.path.exists(prebuilt_path):
1771 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1772 signed_img = MakeTempFile()
1773 shutil.copy(prebuilt_path, signed_img)
1774 partition_name = tree_subdir.lower()
1775 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1776 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001777
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001778 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001779
1780 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001781 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1782 # for recovery.
1783 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1784 prebuilt_name != "boot.img" or
1785 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001786
Doug Zongker6f1d0312014-08-22 08:07:12 -07001787 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001788 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001789 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001790 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001791 if data:
1792 return File(name, data)
1793 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001794
Doug Zongkereef39442009-04-02 12:14:19 -07001795
Steve Mucklee1b10862019-07-10 10:49:37 -07001796def _BuildVendorBootImage(sourcedir, info_dict=None):
1797 """Build a vendor boot image from the specified sourcedir.
1798
1799 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1800 turn them into a vendor boot image.
1801
1802 Return the image data, or None if sourcedir does not appear to contains files
1803 for building the requested image.
1804 """
1805
1806 if info_dict is None:
1807 info_dict = OPTIONS.info_dict
1808
1809 img = tempfile.NamedTemporaryFile()
1810
jiajia tang836f76b2021-04-02 14:48:26 +08001811 ramdisk_format = _GetRamdiskFormat(info_dict)
1812 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001813
1814 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1815 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1816
1817 cmd = [mkbootimg]
1818
1819 fn = os.path.join(sourcedir, "dtb")
1820 if os.access(fn, os.F_OK):
1821 cmd.append("--dtb")
1822 cmd.append(fn)
1823
1824 fn = os.path.join(sourcedir, "vendor_cmdline")
1825 if os.access(fn, os.F_OK):
1826 cmd.append("--vendor_cmdline")
1827 cmd.append(open(fn).read().rstrip("\n"))
1828
1829 fn = os.path.join(sourcedir, "base")
1830 if os.access(fn, os.F_OK):
1831 cmd.append("--base")
1832 cmd.append(open(fn).read().rstrip("\n"))
1833
1834 fn = os.path.join(sourcedir, "pagesize")
1835 if os.access(fn, os.F_OK):
1836 cmd.append("--pagesize")
1837 cmd.append(open(fn).read().rstrip("\n"))
1838
1839 args = info_dict.get("mkbootimg_args")
1840 if args and args.strip():
1841 cmd.extend(shlex.split(args))
1842
1843 args = info_dict.get("mkbootimg_version_args")
1844 if args and args.strip():
1845 cmd.extend(shlex.split(args))
1846
1847 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1848 cmd.extend(["--vendor_boot", img.name])
1849
Devin Moore50509012021-01-13 10:45:04 -08001850 fn = os.path.join(sourcedir, "vendor_bootconfig")
1851 if os.access(fn, os.F_OK):
1852 cmd.append("--vendor_bootconfig")
1853 cmd.append(fn)
1854
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001855 ramdisk_fragment_imgs = []
1856 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1857 if os.access(fn, os.F_OK):
1858 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1859 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001860 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1861 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001862 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001863 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1864 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001865 # Use prebuilt image if found, else create ramdisk from supplied files.
1866 if os.access(fn, os.F_OK):
1867 ramdisk_fragment_pathname = fn
1868 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001869 ramdisk_fragment_root = os.path.join(
1870 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001871 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1872 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001873 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1874 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1875 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1876
Steve Mucklee1b10862019-07-10 10:49:37 -07001877 RunAndCheckOutput(cmd)
1878
1879 # AVB: if enabled, calculate and add hash.
1880 if info_dict.get("avb_enable") == "true":
1881 avbtool = info_dict["avb_avbtool"]
1882 part_size = info_dict["vendor_boot_size"]
1883 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001884 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001885 AppendAVBSigningArgs(cmd, "vendor_boot")
1886 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1887 if args and args.strip():
1888 cmd.extend(shlex.split(args))
1889 RunAndCheckOutput(cmd)
1890
1891 img.seek(os.SEEK_SET, 0)
1892 data = img.read()
1893
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001894 for f in ramdisk_fragment_imgs:
1895 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001896 ramdisk_img.close()
1897 img.close()
1898
1899 return data
1900
1901
1902def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1903 info_dict=None):
1904 """Return a File object with the desired vendor boot image.
1905
1906 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1907 the source files in 'unpack_dir'/'tree_subdir'."""
1908
1909 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1910 if os.path.exists(prebuilt_path):
1911 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1912 return File.FromLocalFile(name, prebuilt_path)
1913
1914 logger.info("building image from target_files %s...", tree_subdir)
1915
1916 if info_dict is None:
1917 info_dict = OPTIONS.info_dict
1918
Kelvin Zhang0876c412020-06-23 15:06:58 -04001919 data = _BuildVendorBootImage(
1920 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001921 if data:
1922 return File(name, data)
1923 return None
1924
1925
Narayan Kamatha07bf042017-08-14 14:49:21 +01001926def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001927 """Gunzips the given gzip compressed file to a given output file."""
1928 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001929 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001930 shutil.copyfileobj(in_file, out_file)
1931
1932
Tao Bao0ff15de2019-03-20 11:26:06 -07001933def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001934 """Unzips the archive to the given directory.
1935
1936 Args:
1937 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001938 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001939 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1940 archvie. Non-matching patterns will be filtered out. If there's no match
1941 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001942 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001943 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001944 if patterns is not None:
1945 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001946 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001947 names = input_zip.namelist()
1948 filtered = [
1949 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1950
1951 # There isn't any matching files. Don't unzip anything.
1952 if not filtered:
1953 return
1954 cmd.extend(filtered)
1955
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001956 RunAndCheckOutput(cmd)
1957
1958
Doug Zongker75f17362009-12-08 13:46:44 -08001959def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001960 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001961
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001962 Args:
1963 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1964 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1965
1966 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1967 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001968
Tao Bao1c830bf2017-12-25 10:43:47 -08001969 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001970 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001971 """
Doug Zongkereef39442009-04-02 12:14:19 -07001972
Tao Bao1c830bf2017-12-25 10:43:47 -08001973 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001974 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1975 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001976 UnzipToDir(m.group(1), tmp, pattern)
1977 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001978 filename = m.group(1)
1979 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001980 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001981
Tao Baodba59ee2018-01-09 13:21:02 -08001982 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001983
1984
Yifan Hong8a66a712019-04-04 15:37:57 -07001985def GetUserImage(which, tmpdir, input_zip,
1986 info_dict=None,
1987 allow_shared_blocks=None,
1988 hashtree_info_generator=None,
1989 reset_file_map=False):
1990 """Returns an Image object suitable for passing to BlockImageDiff.
1991
1992 This function loads the specified image from the given path. If the specified
1993 image is sparse, it also performs additional processing for OTA purpose. For
1994 example, it always adds block 0 to clobbered blocks list. It also detects
1995 files that cannot be reconstructed from the block list, for whom we should
1996 avoid applying imgdiff.
1997
1998 Args:
1999 which: The partition name.
2000 tmpdir: The directory that contains the prebuilt image and block map file.
2001 input_zip: The target-files ZIP archive.
2002 info_dict: The dict to be looked up for relevant info.
2003 allow_shared_blocks: If image is sparse, whether having shared blocks is
2004 allowed. If none, it is looked up from info_dict.
2005 hashtree_info_generator: If present and image is sparse, generates the
2006 hashtree_info for this sparse image.
2007 reset_file_map: If true and image is sparse, reset file map before returning
2008 the image.
2009 Returns:
2010 A Image object. If it is a sparse image and reset_file_map is False, the
2011 image will have file_map info loaded.
2012 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002013 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002014 info_dict = LoadInfoDict(input_zip)
2015
2016 is_sparse = info_dict.get("extfs_sparse_flag")
2017
2018 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2019 # shared blocks (i.e. some blocks will show up in multiple files' block
2020 # list). We can only allocate such shared blocks to the first "owner", and
2021 # disable imgdiff for all later occurrences.
2022 if allow_shared_blocks is None:
2023 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2024
2025 if is_sparse:
2026 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2027 hashtree_info_generator)
2028 if reset_file_map:
2029 img.ResetFileMap()
2030 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002031 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002032
2033
2034def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2035 """Returns a Image object suitable for passing to BlockImageDiff.
2036
2037 This function loads the specified non-sparse image from the given path.
2038
2039 Args:
2040 which: The partition name.
2041 tmpdir: The directory that contains the prebuilt image and block map file.
2042 Returns:
2043 A Image object.
2044 """
2045 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2046 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2047
2048 # The image and map files must have been created prior to calling
2049 # ota_from_target_files.py (since LMP).
2050 assert os.path.exists(path) and os.path.exists(mappath)
2051
Tianjie Xu41976c72019-07-03 13:57:01 -07002052 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2053
Yifan Hong8a66a712019-04-04 15:37:57 -07002054
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002055def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2056 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002057 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2058
2059 This function loads the specified sparse image from the given path, and
2060 performs additional processing for OTA purpose. For example, it always adds
2061 block 0 to clobbered blocks list. It also detects files that cannot be
2062 reconstructed from the block list, for whom we should avoid applying imgdiff.
2063
2064 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002065 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002066 tmpdir: The directory that contains the prebuilt image and block map file.
2067 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002068 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002069 hashtree_info_generator: If present, generates the hashtree_info for this
2070 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002071 Returns:
2072 A SparseImage object, with file_map info loaded.
2073 """
Tao Baoc765cca2018-01-31 17:32:40 -08002074 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2075 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2076
2077 # The image and map files must have been created prior to calling
2078 # ota_from_target_files.py (since LMP).
2079 assert os.path.exists(path) and os.path.exists(mappath)
2080
2081 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2082 # it to clobbered_blocks so that it will be written to the target
2083 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2084 clobbered_blocks = "0"
2085
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002086 image = sparse_img.SparseImage(
2087 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2088 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002089
2090 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2091 # if they contain all zeros. We can't reconstruct such a file from its block
2092 # list. Tag such entries accordingly. (Bug: 65213616)
2093 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002094 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002095 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002096 continue
2097
Tom Cherryd14b8952018-08-09 14:26:00 -07002098 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2099 # filename listed in system.map may contain an additional leading slash
2100 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2101 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002102 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002103 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002104 arcname = entry.lstrip('/')
2105 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002106 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002107 else:
2108 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002109
2110 assert arcname in input_zip.namelist(), \
2111 "Failed to find the ZIP entry for {}".format(entry)
2112
Tao Baoc765cca2018-01-31 17:32:40 -08002113 info = input_zip.getinfo(arcname)
2114 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002115
2116 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002117 # image, check the original block list to determine its completeness. Note
2118 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002119 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002120 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002121
Tao Baoc765cca2018-01-31 17:32:40 -08002122 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2123 ranges.extra['incomplete'] = True
2124
2125 return image
2126
2127
Doug Zongkereef39442009-04-02 12:14:19 -07002128def GetKeyPasswords(keylist):
2129 """Given a list of keys, prompt the user to enter passwords for
2130 those which require them. Return a {key: password} dict. password
2131 will be None if the key has no password."""
2132
Doug Zongker8ce7c252009-05-22 13:34:54 -07002133 no_passwords = []
2134 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002135 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002136 devnull = open("/dev/null", "w+b")
2137 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002138 # We don't need a password for things that aren't really keys.
2139 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002140 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002141 continue
2142
T.R. Fullhart37e10522013-03-18 10:31:26 -07002143 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002144 "-inform", "DER", "-nocrypt"],
2145 stdin=devnull.fileno(),
2146 stdout=devnull.fileno(),
2147 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002148 p.communicate()
2149 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002150 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002151 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002152 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002153 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2154 "-inform", "DER", "-passin", "pass:"],
2155 stdin=devnull.fileno(),
2156 stdout=devnull.fileno(),
2157 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002158 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002159 if p.returncode == 0:
2160 # Encrypted key with empty string as password.
2161 key_passwords[k] = ''
2162 elif stderr.startswith('Error decrypting key'):
2163 # Definitely encrypted key.
2164 # It would have said "Error reading key" if it didn't parse correctly.
2165 need_passwords.append(k)
2166 else:
2167 # Potentially, a type of key that openssl doesn't understand.
2168 # We'll let the routines in signapk.jar handle it.
2169 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002170 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002171
T.R. Fullhart37e10522013-03-18 10:31:26 -07002172 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002173 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002174 return key_passwords
2175
2176
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002177def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002178 """Gets the minSdkVersion declared in the APK.
2179
changho.shin0f125362019-07-08 10:59:00 +09002180 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002181 This can be both a decimal number (API Level) or a codename.
2182
2183 Args:
2184 apk_name: The APK filename.
2185
2186 Returns:
2187 The parsed SDK version string.
2188
2189 Raises:
2190 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002191 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002192 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002193 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002194 stderr=subprocess.PIPE)
2195 stdoutdata, stderrdata = proc.communicate()
2196 if proc.returncode != 0:
2197 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002198 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002199 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002200
Tao Baof47bf0f2018-03-21 23:28:51 -07002201 for line in stdoutdata.split("\n"):
2202 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002203 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2204 if m:
2205 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002206 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002207
2208
2209def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002210 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002211
Tao Baof47bf0f2018-03-21 23:28:51 -07002212 If minSdkVersion is set to a codename, it is translated to a number using the
2213 provided map.
2214
2215 Args:
2216 apk_name: The APK filename.
2217
2218 Returns:
2219 The parsed SDK version number.
2220
2221 Raises:
2222 ExternalError: On failing to get the min SDK version number.
2223 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002224 version = GetMinSdkVersion(apk_name)
2225 try:
2226 return int(version)
2227 except ValueError:
2228 # Not a decimal number. Codename?
2229 if version in codename_to_api_level_map:
2230 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002231 raise ExternalError(
2232 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2233 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002234
2235
2236def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002237 codename_to_api_level_map=None, whole_file=False,
2238 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002239 """Sign the input_name zip/jar/apk, producing output_name. Use the
2240 given key and password (the latter may be None if the key does not
2241 have a password.
2242
Doug Zongker951495f2009-08-14 12:44:19 -07002243 If whole_file is true, use the "-w" option to SignApk to embed a
2244 signature that covers the whole file in the archive comment of the
2245 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002246
2247 min_api_level is the API Level (int) of the oldest platform this file may end
2248 up on. If not specified for an APK, the API Level is obtained by interpreting
2249 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2250
2251 codename_to_api_level_map is needed to translate the codename which may be
2252 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002253
2254 Caller may optionally specify extra args to be passed to SignApk, which
2255 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002256 """
Tao Bao76def242017-11-21 09:25:31 -08002257 if codename_to_api_level_map is None:
2258 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002259 if extra_signapk_args is None:
2260 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002261
Alex Klyubin9667b182015-12-10 13:38:50 -08002262 java_library_path = os.path.join(
2263 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2264
Tao Baoe95540e2016-11-08 12:08:53 -08002265 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2266 ["-Djava.library.path=" + java_library_path,
2267 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002268 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002269 if whole_file:
2270 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002271
2272 min_sdk_version = min_api_level
2273 if min_sdk_version is None:
2274 if not whole_file:
2275 min_sdk_version = GetMinSdkVersionInt(
2276 input_name, codename_to_api_level_map)
2277 if min_sdk_version is not None:
2278 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2279
T.R. Fullhart37e10522013-03-18 10:31:26 -07002280 cmd.extend([key + OPTIONS.public_key_suffix,
2281 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002282 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002283
Tao Bao73dd4f42018-10-04 16:25:33 -07002284 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002285 if password is not None:
2286 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002287 stdoutdata, _ = proc.communicate(password)
2288 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002289 raise ExternalError(
2290 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002291 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002292
Doug Zongkereef39442009-04-02 12:14:19 -07002293
Doug Zongker37974732010-09-16 17:44:38 -07002294def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002295 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002296
Tao Bao9dd909e2017-11-14 11:27:32 -08002297 For non-AVB images, raise exception if the data is too big. Print a warning
2298 if the data is nearing the maximum size.
2299
2300 For AVB images, the actual image size should be identical to the limit.
2301
2302 Args:
2303 data: A string that contains all the data for the partition.
2304 target: The partition name. The ".img" suffix is optional.
2305 info_dict: The dict to be looked up for relevant info.
2306 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002307 if target.endswith(".img"):
2308 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002309 mount_point = "/" + target
2310
Ying Wangf8824af2014-06-03 14:07:27 -07002311 fs_type = None
2312 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002313 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002314 if mount_point == "/userdata":
2315 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002316 p = info_dict["fstab"][mount_point]
2317 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002318 device = p.device
2319 if "/" in device:
2320 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002321 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002322 if not fs_type or not limit:
2323 return
Doug Zongkereef39442009-04-02 12:14:19 -07002324
Andrew Boie0f9aec82012-02-14 09:32:52 -08002325 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002326 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2327 # path.
2328 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2329 if size != limit:
2330 raise ExternalError(
2331 "Mismatching image size for %s: expected %d actual %d" % (
2332 target, limit, size))
2333 else:
2334 pct = float(size) * 100.0 / limit
2335 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2336 if pct >= 99.0:
2337 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002338
2339 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002340 logger.warning("\n WARNING: %s\n", msg)
2341 else:
2342 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002343
2344
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002345def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002346 """Parses the APK certs info from a given target-files zip.
2347
2348 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2349 tuple with the following elements: (1) a dictionary that maps packages to
2350 certs (based on the "certificate" and "private_key" attributes in the file;
2351 (2) a string representing the extension of compressed APKs in the target files
2352 (e.g ".gz", ".bro").
2353
2354 Args:
2355 tf_zip: The input target_files ZipFile (already open).
2356
2357 Returns:
2358 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2359 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2360 no compressed APKs.
2361 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002362 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002363 compressed_extension = None
2364
Tao Bao0f990332017-09-08 19:02:54 -07002365 # META/apkcerts.txt contains the info for _all_ the packages known at build
2366 # time. Filter out the ones that are not installed.
2367 installed_files = set()
2368 for name in tf_zip.namelist():
2369 basename = os.path.basename(name)
2370 if basename:
2371 installed_files.add(basename)
2372
Tao Baoda30cfa2017-12-01 16:19:46 -08002373 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002374 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002375 if not line:
2376 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002377 m = re.match(
2378 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002379 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2380 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002381 line)
2382 if not m:
2383 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002384
Tao Bao818ddf52018-01-05 11:17:34 -08002385 matches = m.groupdict()
2386 cert = matches["CERT"]
2387 privkey = matches["PRIVKEY"]
2388 name = matches["NAME"]
2389 this_compressed_extension = matches["COMPRESSED"]
2390
2391 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2392 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2393 if cert in SPECIAL_CERT_STRINGS and not privkey:
2394 certmap[name] = cert
2395 elif (cert.endswith(OPTIONS.public_key_suffix) and
2396 privkey.endswith(OPTIONS.private_key_suffix) and
2397 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2398 certmap[name] = cert[:-public_key_suffix_len]
2399 else:
2400 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2401
2402 if not this_compressed_extension:
2403 continue
2404
2405 # Only count the installed files.
2406 filename = name + '.' + this_compressed_extension
2407 if filename not in installed_files:
2408 continue
2409
2410 # Make sure that all the values in the compression map have the same
2411 # extension. We don't support multiple compression methods in the same
2412 # system image.
2413 if compressed_extension:
2414 if this_compressed_extension != compressed_extension:
2415 raise ValueError(
2416 "Multiple compressed extensions: {} vs {}".format(
2417 compressed_extension, this_compressed_extension))
2418 else:
2419 compressed_extension = this_compressed_extension
2420
2421 return (certmap,
2422 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002423
2424
Doug Zongkereef39442009-04-02 12:14:19 -07002425COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002426Global options
2427
2428 -p (--path) <dir>
2429 Prepend <dir>/bin to the list of places to search for binaries run by this
2430 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002431
Doug Zongker05d3dea2009-06-22 11:32:31 -07002432 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002433 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002434
Tao Bao30df8b42018-04-23 15:32:53 -07002435 -x (--extra) <key=value>
2436 Add a key/value pair to the 'extras' dict, which device-specific extension
2437 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002438
Doug Zongkereef39442009-04-02 12:14:19 -07002439 -v (--verbose)
2440 Show command lines being executed.
2441
2442 -h (--help)
2443 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002444
2445 --logfile <file>
2446 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002447"""
2448
Kelvin Zhang0876c412020-06-23 15:06:58 -04002449
Doug Zongkereef39442009-04-02 12:14:19 -07002450def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002451 print(docstring.rstrip("\n"))
2452 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002453
2454
2455def ParseOptions(argv,
2456 docstring,
2457 extra_opts="", extra_long_opts=(),
2458 extra_option_handler=None):
2459 """Parse the options in argv and return any arguments that aren't
2460 flags. docstring is the calling module's docstring, to be displayed
2461 for errors and -h. extra_opts and extra_long_opts are for flags
2462 defined by the caller, which are processed by passing them to
2463 extra_option_handler."""
2464
2465 try:
2466 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002467 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002468 ["help", "verbose", "path=", "signapk_path=",
2469 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002470 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002471 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2472 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002473 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2474 "aftl_key_path=", "aftl_manufacturer_key_path=",
2475 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002476 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002477 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002478 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002479 sys.exit(2)
2480
Doug Zongkereef39442009-04-02 12:14:19 -07002481 for o, a in opts:
2482 if o in ("-h", "--help"):
2483 Usage(docstring)
2484 sys.exit()
2485 elif o in ("-v", "--verbose"):
2486 OPTIONS.verbose = True
2487 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002488 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002489 elif o in ("--signapk_path",):
2490 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002491 elif o in ("--signapk_shared_library_path",):
2492 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002493 elif o in ("--extra_signapk_args",):
2494 OPTIONS.extra_signapk_args = shlex.split(a)
2495 elif o in ("--java_path",):
2496 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002497 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002498 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002499 elif o in ("--android_jar_path",):
2500 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002501 elif o in ("--public_key_suffix",):
2502 OPTIONS.public_key_suffix = a
2503 elif o in ("--private_key_suffix",):
2504 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002505 elif o in ("--boot_signer_path",):
2506 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002507 elif o in ("--boot_signer_args",):
2508 OPTIONS.boot_signer_args = shlex.split(a)
2509 elif o in ("--verity_signer_path",):
2510 OPTIONS.verity_signer_path = a
2511 elif o in ("--verity_signer_args",):
2512 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002513 elif o in ("--aftl_tool_path",):
2514 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002515 elif o in ("--aftl_server",):
2516 OPTIONS.aftl_server = a
2517 elif o in ("--aftl_key_path",):
2518 OPTIONS.aftl_key_path = a
2519 elif o in ("--aftl_manufacturer_key_path",):
2520 OPTIONS.aftl_manufacturer_key_path = a
2521 elif o in ("--aftl_signer_helper",):
2522 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002523 elif o in ("-s", "--device_specific"):
2524 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002525 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002526 key, value = a.split("=", 1)
2527 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002528 elif o in ("--logfile",):
2529 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002530 else:
2531 if extra_option_handler is None or not extra_option_handler(o, a):
2532 assert False, "unknown option \"%s\"" % (o,)
2533
Doug Zongker85448772014-09-09 14:59:20 -07002534 if OPTIONS.search_path:
2535 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2536 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002537
2538 return args
2539
2540
Tao Bao4c851b12016-09-19 13:54:38 -07002541def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002542 """Make a temp file and add it to the list of things to be deleted
2543 when Cleanup() is called. Return the filename."""
2544 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2545 os.close(fd)
2546 OPTIONS.tempfiles.append(fn)
2547 return fn
2548
2549
Tao Bao1c830bf2017-12-25 10:43:47 -08002550def MakeTempDir(prefix='tmp', suffix=''):
2551 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2552
2553 Returns:
2554 The absolute pathname of the new directory.
2555 """
2556 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2557 OPTIONS.tempfiles.append(dir_name)
2558 return dir_name
2559
2560
Doug Zongkereef39442009-04-02 12:14:19 -07002561def Cleanup():
2562 for i in OPTIONS.tempfiles:
2563 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002564 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002565 else:
2566 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002567 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002568
2569
2570class PasswordManager(object):
2571 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002572 self.editor = os.getenv("EDITOR")
2573 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002574
2575 def GetPasswords(self, items):
2576 """Get passwords corresponding to each string in 'items',
2577 returning a dict. (The dict may have keys in addition to the
2578 values in 'items'.)
2579
2580 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2581 user edit that file to add more needed passwords. If no editor is
2582 available, or $ANDROID_PW_FILE isn't define, prompts the user
2583 interactively in the ordinary way.
2584 """
2585
2586 current = self.ReadFile()
2587
2588 first = True
2589 while True:
2590 missing = []
2591 for i in items:
2592 if i not in current or not current[i]:
2593 missing.append(i)
2594 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002595 if not missing:
2596 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002597
2598 for i in missing:
2599 current[i] = ""
2600
2601 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002602 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002603 if sys.version_info[0] >= 3:
2604 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002605 answer = raw_input("try to edit again? [y]> ").strip()
2606 if answer and answer[0] not in 'yY':
2607 raise RuntimeError("key passwords unavailable")
2608 first = False
2609
2610 current = self.UpdateAndReadFile(current)
2611
Kelvin Zhang0876c412020-06-23 15:06:58 -04002612 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002613 """Prompt the user to enter a value (password) for each key in
2614 'current' whose value is fales. Returns a new dict with all the
2615 values.
2616 """
2617 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002618 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002619 if v:
2620 result[k] = v
2621 else:
2622 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002623 result[k] = getpass.getpass(
2624 "Enter password for %s key> " % k).strip()
2625 if result[k]:
2626 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002627 return result
2628
2629 def UpdateAndReadFile(self, current):
2630 if not self.editor or not self.pwfile:
2631 return self.PromptResult(current)
2632
2633 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002634 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002635 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2636 f.write("# (Additional spaces are harmless.)\n\n")
2637
2638 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002639 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002640 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002641 f.write("[[[ %s ]]] %s\n" % (v, k))
2642 if not v and first_line is None:
2643 # position cursor on first line with no password.
2644 first_line = i + 4
2645 f.close()
2646
Tao Bao986ee862018-10-04 15:46:16 -07002647 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002648
2649 return self.ReadFile()
2650
2651 def ReadFile(self):
2652 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002653 if self.pwfile is None:
2654 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002655 try:
2656 f = open(self.pwfile, "r")
2657 for line in f:
2658 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002659 if not line or line[0] == '#':
2660 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002661 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2662 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002663 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002664 else:
2665 result[m.group(2)] = m.group(1)
2666 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002667 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002668 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002669 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002670 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002671
2672
Dan Albert8e0178d2015-01-27 15:53:15 -08002673def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2674 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002675
2676 # http://b/18015246
2677 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2678 # for files larger than 2GiB. We can work around this by adjusting their
2679 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2680 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2681 # it isn't clear to me exactly what circumstances cause this).
2682 # `zipfile.write()` must be used directly to work around this.
2683 #
2684 # This mess can be avoided if we port to python3.
2685 saved_zip64_limit = zipfile.ZIP64_LIMIT
2686 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2687
2688 if compress_type is None:
2689 compress_type = zip_file.compression
2690 if arcname is None:
2691 arcname = filename
2692
2693 saved_stat = os.stat(filename)
2694
2695 try:
2696 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2697 # file to be zipped and reset it when we're done.
2698 os.chmod(filename, perms)
2699
2700 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002701 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2702 # intentional. zip stores datetimes in local time without a time zone
2703 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2704 # in the zip archive.
2705 local_epoch = datetime.datetime.fromtimestamp(0)
2706 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002707 os.utime(filename, (timestamp, timestamp))
2708
2709 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2710 finally:
2711 os.chmod(filename, saved_stat.st_mode)
2712 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2713 zipfile.ZIP64_LIMIT = saved_zip64_limit
2714
2715
Tao Bao58c1b962015-05-20 09:32:18 -07002716def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002717 compress_type=None):
2718 """Wrap zipfile.writestr() function to work around the zip64 limit.
2719
2720 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2721 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2722 when calling crc32(bytes).
2723
2724 But it still works fine to write a shorter string into a large zip file.
2725 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2726 when we know the string won't be too long.
2727 """
2728
2729 saved_zip64_limit = zipfile.ZIP64_LIMIT
2730 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2731
2732 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2733 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002734 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002735 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002736 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002737 else:
Tao Baof3282b42015-04-01 11:21:55 -07002738 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002739 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2740 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2741 # such a case (since
2742 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2743 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2744 # permission bits. We follow the logic in Python 3 to get consistent
2745 # behavior between using the two versions.
2746 if not zinfo.external_attr:
2747 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002748
2749 # If compress_type is given, it overrides the value in zinfo.
2750 if compress_type is not None:
2751 zinfo.compress_type = compress_type
2752
Tao Bao58c1b962015-05-20 09:32:18 -07002753 # If perms is given, it has a priority.
2754 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002755 # If perms doesn't set the file type, mark it as a regular file.
2756 if perms & 0o770000 == 0:
2757 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002758 zinfo.external_attr = perms << 16
2759
Tao Baof3282b42015-04-01 11:21:55 -07002760 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002761 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2762
Dan Albert8b72aef2015-03-23 19:13:21 -07002763 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002764 zipfile.ZIP64_LIMIT = saved_zip64_limit
2765
2766
Tao Bao89d7ab22017-12-14 17:05:33 -08002767def ZipDelete(zip_filename, entries):
2768 """Deletes entries from a ZIP file.
2769
2770 Since deleting entries from a ZIP file is not supported, it shells out to
2771 'zip -d'.
2772
2773 Args:
2774 zip_filename: The name of the ZIP file.
2775 entries: The name of the entry, or the list of names to be deleted.
2776
2777 Raises:
2778 AssertionError: In case of non-zero return from 'zip'.
2779 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002780 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002781 entries = [entries]
2782 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002783 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002784
2785
Tao Baof3282b42015-04-01 11:21:55 -07002786def ZipClose(zip_file):
2787 # http://b/18015246
2788 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2789 # central directory.
2790 saved_zip64_limit = zipfile.ZIP64_LIMIT
2791 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2792
2793 zip_file.close()
2794
2795 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002796
2797
2798class DeviceSpecificParams(object):
2799 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002800
Doug Zongker05d3dea2009-06-22 11:32:31 -07002801 def __init__(self, **kwargs):
2802 """Keyword arguments to the constructor become attributes of this
2803 object, which is passed to all functions in the device-specific
2804 module."""
Tao Bao38884282019-07-10 22:20:56 -07002805 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002806 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002807 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002808
2809 if self.module is None:
2810 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002811 if not path:
2812 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002813 try:
2814 if os.path.isdir(path):
2815 info = imp.find_module("releasetools", [path])
2816 else:
2817 d, f = os.path.split(path)
2818 b, x = os.path.splitext(f)
2819 if x == ".py":
2820 f = b
2821 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002822 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002823 self.module = imp.load_module("device_specific", *info)
2824 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002825 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002826
2827 def _DoCall(self, function_name, *args, **kwargs):
2828 """Call the named function in the device-specific module, passing
2829 the given args and kwargs. The first argument to the call will be
2830 the DeviceSpecific object itself. If there is no module, or the
2831 module does not define the function, return the value of the
2832 'default' kwarg (which itself defaults to None)."""
2833 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002834 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002835 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2836
2837 def FullOTA_Assertions(self):
2838 """Called after emitting the block of assertions at the top of a
2839 full OTA package. Implementations can add whatever additional
2840 assertions they like."""
2841 return self._DoCall("FullOTA_Assertions")
2842
Doug Zongkere5ff5902012-01-17 10:55:37 -08002843 def FullOTA_InstallBegin(self):
2844 """Called at the start of full OTA installation."""
2845 return self._DoCall("FullOTA_InstallBegin")
2846
Yifan Hong10c530d2018-12-27 17:34:18 -08002847 def FullOTA_GetBlockDifferences(self):
2848 """Called during full OTA installation and verification.
2849 Implementation should return a list of BlockDifference objects describing
2850 the update on each additional partitions.
2851 """
2852 return self._DoCall("FullOTA_GetBlockDifferences")
2853
Doug Zongker05d3dea2009-06-22 11:32:31 -07002854 def FullOTA_InstallEnd(self):
2855 """Called at the end of full OTA installation; typically this is
2856 used to install the image for the device's baseband processor."""
2857 return self._DoCall("FullOTA_InstallEnd")
2858
2859 def IncrementalOTA_Assertions(self):
2860 """Called after emitting the block of assertions at the top of an
2861 incremental OTA package. Implementations can add whatever
2862 additional assertions they like."""
2863 return self._DoCall("IncrementalOTA_Assertions")
2864
Doug Zongkere5ff5902012-01-17 10:55:37 -08002865 def IncrementalOTA_VerifyBegin(self):
2866 """Called at the start of the verification phase of incremental
2867 OTA installation; additional checks can be placed here to abort
2868 the script before any changes are made."""
2869 return self._DoCall("IncrementalOTA_VerifyBegin")
2870
Doug Zongker05d3dea2009-06-22 11:32:31 -07002871 def IncrementalOTA_VerifyEnd(self):
2872 """Called at the end of the verification phase of incremental OTA
2873 installation; additional checks can be placed here to abort the
2874 script before any changes are made."""
2875 return self._DoCall("IncrementalOTA_VerifyEnd")
2876
Doug Zongkere5ff5902012-01-17 10:55:37 -08002877 def IncrementalOTA_InstallBegin(self):
2878 """Called at the start of incremental OTA installation (after
2879 verification is complete)."""
2880 return self._DoCall("IncrementalOTA_InstallBegin")
2881
Yifan Hong10c530d2018-12-27 17:34:18 -08002882 def IncrementalOTA_GetBlockDifferences(self):
2883 """Called during incremental OTA installation and verification.
2884 Implementation should return a list of BlockDifference objects describing
2885 the update on each additional partitions.
2886 """
2887 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2888
Doug Zongker05d3dea2009-06-22 11:32:31 -07002889 def IncrementalOTA_InstallEnd(self):
2890 """Called at the end of incremental OTA installation; typically
2891 this is used to install the image for the device's baseband
2892 processor."""
2893 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002894
Tao Bao9bc6bb22015-11-09 16:58:28 -08002895 def VerifyOTA_Assertions(self):
2896 return self._DoCall("VerifyOTA_Assertions")
2897
Tao Bao76def242017-11-21 09:25:31 -08002898
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002899class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002900 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002901 self.name = name
2902 self.data = data
2903 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002904 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002905 self.sha1 = sha1(data).hexdigest()
2906
2907 @classmethod
2908 def FromLocalFile(cls, name, diskname):
2909 f = open(diskname, "rb")
2910 data = f.read()
2911 f.close()
2912 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002913
2914 def WriteToTemp(self):
2915 t = tempfile.NamedTemporaryFile()
2916 t.write(self.data)
2917 t.flush()
2918 return t
2919
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002920 def WriteToDir(self, d):
2921 with open(os.path.join(d, self.name), "wb") as fp:
2922 fp.write(self.data)
2923
Geremy Condra36bd3652014-02-06 19:45:10 -08002924 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002925 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002926
Tao Bao76def242017-11-21 09:25:31 -08002927
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002928DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002929 ".gz": "imgdiff",
2930 ".zip": ["imgdiff", "-z"],
2931 ".jar": ["imgdiff", "-z"],
2932 ".apk": ["imgdiff", "-z"],
2933 ".img": "imgdiff",
2934}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002935
Tao Bao76def242017-11-21 09:25:31 -08002936
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002937class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002938 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002939 self.tf = tf
2940 self.sf = sf
2941 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002942 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002943
2944 def ComputePatch(self):
2945 """Compute the patch (as a string of data) needed to turn sf into
2946 tf. Returns the same tuple as GetPatch()."""
2947
2948 tf = self.tf
2949 sf = self.sf
2950
Doug Zongker24cd2802012-08-14 16:36:15 -07002951 if self.diff_program:
2952 diff_program = self.diff_program
2953 else:
2954 ext = os.path.splitext(tf.name)[1]
2955 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002956
2957 ttemp = tf.WriteToTemp()
2958 stemp = sf.WriteToTemp()
2959
2960 ext = os.path.splitext(tf.name)[1]
2961
2962 try:
2963 ptemp = tempfile.NamedTemporaryFile()
2964 if isinstance(diff_program, list):
2965 cmd = copy.copy(diff_program)
2966 else:
2967 cmd = [diff_program]
2968 cmd.append(stemp.name)
2969 cmd.append(ttemp.name)
2970 cmd.append(ptemp.name)
2971 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002972 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002973
Doug Zongkerf8340082014-08-05 10:39:37 -07002974 def run():
2975 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002976 if e:
2977 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002978 th = threading.Thread(target=run)
2979 th.start()
2980 th.join(timeout=300) # 5 mins
2981 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002982 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002983 p.terminate()
2984 th.join(5)
2985 if th.is_alive():
2986 p.kill()
2987 th.join()
2988
Tianjie Xua2a9f992018-01-05 15:15:54 -08002989 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002990 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002991 self.patch = None
2992 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002993 diff = ptemp.read()
2994 finally:
2995 ptemp.close()
2996 stemp.close()
2997 ttemp.close()
2998
2999 self.patch = diff
3000 return self.tf, self.sf, self.patch
3001
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003002 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003003 """Returns a tuple of (target_file, source_file, patch_data).
3004
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003005 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003006 computing the patch failed.
3007 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003008 return self.tf, self.sf, self.patch
3009
3010
3011def ComputeDifferences(diffs):
3012 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003013 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003014
3015 # Do the largest files first, to try and reduce the long-pole effect.
3016 by_size = [(i.tf.size, i) for i in diffs]
3017 by_size.sort(reverse=True)
3018 by_size = [i[1] for i in by_size]
3019
3020 lock = threading.Lock()
3021 diff_iter = iter(by_size) # accessed under lock
3022
3023 def worker():
3024 try:
3025 lock.acquire()
3026 for d in diff_iter:
3027 lock.release()
3028 start = time.time()
3029 d.ComputePatch()
3030 dur = time.time() - start
3031 lock.acquire()
3032
3033 tf, sf, patch = d.GetPatch()
3034 if sf.name == tf.name:
3035 name = tf.name
3036 else:
3037 name = "%s (%s)" % (tf.name, sf.name)
3038 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003039 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003040 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003041 logger.info(
3042 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3043 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003044 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003045 except Exception:
3046 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003047 raise
3048
3049 # start worker threads; wait for them all to finish.
3050 threads = [threading.Thread(target=worker)
3051 for i in range(OPTIONS.worker_threads)]
3052 for th in threads:
3053 th.start()
3054 while threads:
3055 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003056
3057
Dan Albert8b72aef2015-03-23 19:13:21 -07003058class BlockDifference(object):
3059 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003060 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003061 self.tgt = tgt
3062 self.src = src
3063 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003064 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003065 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003066
Tao Baodd2a5892015-03-12 12:32:37 -07003067 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003068 version = max(
3069 int(i) for i in
3070 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003071 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003072 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003073
Tianjie Xu41976c72019-07-03 13:57:01 -07003074 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3075 version=self.version,
3076 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003077 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003078 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003079 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003080 self.touched_src_ranges = b.touched_src_ranges
3081 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003082
Yifan Hong10c530d2018-12-27 17:34:18 -08003083 # On devices with dynamic partitions, for new partitions,
3084 # src is None but OPTIONS.source_info_dict is not.
3085 if OPTIONS.source_info_dict is None:
3086 is_dynamic_build = OPTIONS.info_dict.get(
3087 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003088 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003089 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003090 is_dynamic_build = OPTIONS.source_info_dict.get(
3091 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003092 is_dynamic_source = partition in shlex.split(
3093 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003094
Yifan Hongbb2658d2019-01-25 12:30:58 -08003095 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003096 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3097
Yifan Hongbb2658d2019-01-25 12:30:58 -08003098 # For dynamic partitions builds, check partition list in both source
3099 # and target build because new partitions may be added, and existing
3100 # partitions may be removed.
3101 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3102
Yifan Hong10c530d2018-12-27 17:34:18 -08003103 if is_dynamic:
3104 self.device = 'map_partition("%s")' % partition
3105 else:
3106 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003107 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3108 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003109 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003110 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3111 OPTIONS.source_info_dict)
3112 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003113
Tao Baod8d14be2016-02-04 14:26:02 -08003114 @property
3115 def required_cache(self):
3116 return self._required_cache
3117
Tao Bao76def242017-11-21 09:25:31 -08003118 def WriteScript(self, script, output_zip, progress=None,
3119 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003120 if not self.src:
3121 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003122 script.Print("Patching %s image unconditionally..." % (self.partition,))
3123 else:
3124 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003125
Dan Albert8b72aef2015-03-23 19:13:21 -07003126 if progress:
3127 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003128 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003129
3130 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003131 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003132
Tao Bao9bc6bb22015-11-09 16:58:28 -08003133 def WriteStrictVerifyScript(self, script):
3134 """Verify all the blocks in the care_map, including clobbered blocks.
3135
3136 This differs from the WriteVerifyScript() function: a) it prints different
3137 error messages; b) it doesn't allow half-way updated images to pass the
3138 verification."""
3139
3140 partition = self.partition
3141 script.Print("Verifying %s..." % (partition,))
3142 ranges = self.tgt.care_map
3143 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003144 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003145 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3146 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003147 self.device, ranges_str,
3148 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003149 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003150 script.AppendExtra("")
3151
Tao Baod522bdc2016-04-12 15:53:16 -07003152 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003153 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003154
3155 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003156 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003157 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003158
3159 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003160 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003161 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003162 ranges = self.touched_src_ranges
3163 expected_sha1 = self.touched_src_sha1
3164 else:
3165 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3166 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003167
3168 # No blocks to be checked, skipping.
3169 if not ranges:
3170 return
3171
Tao Bao5ece99d2015-05-12 11:42:31 -07003172 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003173 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003174 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003175 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3176 '"%s.patch.dat")) then' % (
3177 self.device, ranges_str, expected_sha1,
3178 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003179 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003180 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003181
Tianjie Xufc3422a2015-12-15 11:53:59 -08003182 if self.version >= 4:
3183
3184 # Bug: 21124327
3185 # When generating incrementals for the system and vendor partitions in
3186 # version 4 or newer, explicitly check the first block (which contains
3187 # the superblock) of the partition to see if it's what we expect. If
3188 # this check fails, give an explicit log message about the partition
3189 # having been remounted R/W (the most likely explanation).
3190 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003191 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003192
3193 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003194 if partition == "system":
3195 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3196 else:
3197 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003198 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003199 'ifelse (block_image_recover({device}, "{ranges}") && '
3200 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003201 'package_extract_file("{partition}.transfer.list"), '
3202 '"{partition}.new.dat", "{partition}.patch.dat"), '
3203 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003204 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003205 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003206 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003207
Tao Baodd2a5892015-03-12 12:32:37 -07003208 # Abort the OTA update. Note that the incremental OTA cannot be applied
3209 # even if it may match the checksum of the target partition.
3210 # a) If version < 3, operations like move and erase will make changes
3211 # unconditionally and damage the partition.
3212 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003213 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003214 if partition == "system":
3215 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3216 else:
3217 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3218 script.AppendExtra((
3219 'abort("E%d: %s partition has unexpected contents");\n'
3220 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003221
Yifan Hong10c530d2018-12-27 17:34:18 -08003222 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003223 partition = self.partition
3224 script.Print('Verifying the updated %s image...' % (partition,))
3225 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3226 ranges = self.tgt.care_map
3227 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003228 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003229 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003230 self.device, ranges_str,
3231 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003232
3233 # Bug: 20881595
3234 # Verify that extended blocks are really zeroed out.
3235 if self.tgt.extended:
3236 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003237 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003238 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003239 self.device, ranges_str,
3240 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003241 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003242 if partition == "system":
3243 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3244 else:
3245 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003246 script.AppendExtra(
3247 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003248 ' abort("E%d: %s partition has unexpected non-zero contents after '
3249 'OTA update");\n'
3250 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003251 else:
3252 script.Print('Verified the updated %s image.' % (partition,))
3253
Tianjie Xu209db462016-05-24 17:34:52 -07003254 if partition == "system":
3255 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3256 else:
3257 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3258
Tao Bao5fcaaef2015-06-01 13:40:49 -07003259 script.AppendExtra(
3260 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003261 ' abort("E%d: %s partition has unexpected contents after OTA '
3262 'update");\n'
3263 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003264
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003265 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003266 ZipWrite(output_zip,
3267 '{}.transfer.list'.format(self.path),
3268 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003269
Tao Bao76def242017-11-21 09:25:31 -08003270 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3271 # its size. Quailty 9 almost triples the compression time but doesn't
3272 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003273 # zip | brotli(quality 6) | brotli(quality 9)
3274 # compressed_size: 942M | 869M (~8% reduced) | 854M
3275 # compression_time: 75s | 265s | 719s
3276 # decompression_time: 15s | 25s | 25s
3277
3278 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003279 brotli_cmd = ['brotli', '--quality=6',
3280 '--output={}.new.dat.br'.format(self.path),
3281 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003282 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003283 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003284
3285 new_data_name = '{}.new.dat.br'.format(self.partition)
3286 ZipWrite(output_zip,
3287 '{}.new.dat.br'.format(self.path),
3288 new_data_name,
3289 compress_type=zipfile.ZIP_STORED)
3290 else:
3291 new_data_name = '{}.new.dat'.format(self.partition)
3292 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3293
Dan Albert8e0178d2015-01-27 15:53:15 -08003294 ZipWrite(output_zip,
3295 '{}.patch.dat'.format(self.path),
3296 '{}.patch.dat'.format(self.partition),
3297 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003298
Tianjie Xu209db462016-05-24 17:34:52 -07003299 if self.partition == "system":
3300 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3301 else:
3302 code = ErrorCode.VENDOR_UPDATE_FAILURE
3303
Yifan Hong10c530d2018-12-27 17:34:18 -08003304 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003305 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003306 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003307 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003308 device=self.device, partition=self.partition,
3309 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003310 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003311
Kelvin Zhang0876c412020-06-23 15:06:58 -04003312 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003313 data = source.ReadRangeSet(ranges)
3314 ctx = sha1()
3315
3316 for p in data:
3317 ctx.update(p)
3318
3319 return ctx.hexdigest()
3320
Kelvin Zhang0876c412020-06-23 15:06:58 -04003321 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003322 """Return the hash value for all zero blocks."""
3323 zero_block = '\x00' * 4096
3324 ctx = sha1()
3325 for _ in range(num_blocks):
3326 ctx.update(zero_block)
3327
3328 return ctx.hexdigest()
3329
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003330
Tianjie Xu41976c72019-07-03 13:57:01 -07003331# Expose these two classes to support vendor-specific scripts
3332DataImage = images.DataImage
3333EmptyImage = images.EmptyImage
3334
Tao Bao76def242017-11-21 09:25:31 -08003335
Doug Zongker96a57e72010-09-26 14:57:41 -07003336# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003337PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003338 "ext4": "EMMC",
3339 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003340 "f2fs": "EMMC",
3341 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003342}
Doug Zongker96a57e72010-09-26 14:57:41 -07003343
Kelvin Zhang0876c412020-06-23 15:06:58 -04003344
Yifan Hongbdb32012020-05-07 12:38:53 -07003345def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3346 """
3347 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3348 backwards compatibility. It aborts if the fstab entry has slotselect option
3349 (unless check_no_slot is explicitly set to False).
3350 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003351 fstab = info["fstab"]
3352 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003353 if check_no_slot:
3354 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003355 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003356 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3357 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003358 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003359
3360
Yifan Hongbdb32012020-05-07 12:38:53 -07003361def GetTypeAndDeviceExpr(mount_point, info):
3362 """
3363 Return the filesystem of the partition, and an edify expression that evaluates
3364 to the device at runtime.
3365 """
3366 fstab = info["fstab"]
3367 if fstab:
3368 p = fstab[mount_point]
3369 device_expr = '"%s"' % fstab[mount_point].device
3370 if p.slotselect:
3371 device_expr = 'add_slot_suffix(%s)' % device_expr
3372 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003373 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003374
3375
3376def GetEntryForDevice(fstab, device):
3377 """
3378 Returns:
3379 The first entry in fstab whose device is the given value.
3380 """
3381 if not fstab:
3382 return None
3383 for mount_point in fstab:
3384 if fstab[mount_point].device == device:
3385 return fstab[mount_point]
3386 return None
3387
Kelvin Zhang0876c412020-06-23 15:06:58 -04003388
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003389def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003390 """Parses and converts a PEM-encoded certificate into DER-encoded.
3391
3392 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3393
3394 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003395 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003396 """
3397 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003398 save = False
3399 for line in data.split("\n"):
3400 if "--END CERTIFICATE--" in line:
3401 break
3402 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003403 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003404 if "--BEGIN CERTIFICATE--" in line:
3405 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003406 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003407 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003408
Tao Bao04e1f012018-02-04 12:13:35 -08003409
3410def ExtractPublicKey(cert):
3411 """Extracts the public key (PEM-encoded) from the given certificate file.
3412
3413 Args:
3414 cert: The certificate filename.
3415
3416 Returns:
3417 The public key string.
3418
3419 Raises:
3420 AssertionError: On non-zero return from 'openssl'.
3421 """
3422 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3423 # While openssl 1.1 writes the key into the given filename followed by '-out',
3424 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3425 # stdout instead.
3426 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3427 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3428 pubkey, stderrdata = proc.communicate()
3429 assert proc.returncode == 0, \
3430 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3431 return pubkey
3432
3433
Tao Bao1ac886e2019-06-26 11:58:22 -07003434def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003435 """Extracts the AVB public key from the given public or private key.
3436
3437 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003438 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003439 key: The input key file, which should be PEM-encoded public or private key.
3440
3441 Returns:
3442 The path to the extracted AVB public key file.
3443 """
3444 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3445 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003446 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003447 return output
3448
3449
Doug Zongker412c02f2014-02-13 10:58:24 -08003450def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3451 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003452 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003453
Tao Bao6d5d6232018-03-09 17:04:42 -08003454 Most of the space in the boot and recovery images is just the kernel, which is
3455 identical for the two, so the resulting patch should be efficient. Add it to
3456 the output zip, along with a shell script that is run from init.rc on first
3457 boot to actually do the patching and install the new recovery image.
3458
3459 Args:
3460 input_dir: The top-level input directory of the target-files.zip.
3461 output_sink: The callback function that writes the result.
3462 recovery_img: File object for the recovery image.
3463 boot_img: File objects for the boot image.
3464 info_dict: A dict returned by common.LoadInfoDict() on the input
3465 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003466 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003467 if info_dict is None:
3468 info_dict = OPTIONS.info_dict
3469
Tao Bao6d5d6232018-03-09 17:04:42 -08003470 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003471 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3472
3473 if board_uses_vendorimage:
3474 # In this case, the output sink is rooted at VENDOR
3475 recovery_img_path = "etc/recovery.img"
3476 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3477 sh_dir = "bin"
3478 else:
3479 # In this case the output sink is rooted at SYSTEM
3480 recovery_img_path = "vendor/etc/recovery.img"
3481 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3482 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003483
Tao Baof2cffbd2015-07-22 12:33:18 -07003484 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003485 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003486
3487 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003488 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003489 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003490 # With system-root-image, boot and recovery images will have mismatching
3491 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3492 # to handle such a case.
3493 if system_root_image:
3494 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003495 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003496 assert not os.path.exists(path)
3497 else:
3498 diff_program = ["imgdiff"]
3499 if os.path.exists(path):
3500 diff_program.append("-b")
3501 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003502 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003503 else:
3504 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003505
3506 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3507 _, _, patch = d.ComputePatch()
3508 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003509
Dan Albertebb19aa2015-03-27 19:11:53 -07003510 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003511 # The following GetTypeAndDevice()s need to use the path in the target
3512 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003513 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3514 check_no_slot=False)
3515 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3516 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003517 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003518 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003519
Tao Baof2cffbd2015-07-22 12:33:18 -07003520 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003521
3522 # Note that we use /vendor to refer to the recovery resources. This will
3523 # work for a separate vendor partition mounted at /vendor or a
3524 # /system/vendor subdirectory on the system partition, for which init will
3525 # create a symlink from /vendor to /system/vendor.
3526
3527 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003528if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3529 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003530 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003531 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3532 log -t recovery "Installing new recovery image: succeeded" || \\
3533 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003534else
3535 log -t recovery "Recovery image already installed"
3536fi
3537""" % {'type': recovery_type,
3538 'device': recovery_device,
3539 'sha1': recovery_img.sha1,
3540 'size': recovery_img.size}
3541 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003542 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003543if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3544 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003545 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003546 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3547 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3548 log -t recovery "Installing new recovery image: succeeded" || \\
3549 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003550else
3551 log -t recovery "Recovery image already installed"
3552fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003553""" % {'boot_size': boot_img.size,
3554 'boot_sha1': boot_img.sha1,
3555 'recovery_size': recovery_img.size,
3556 'recovery_sha1': recovery_img.sha1,
3557 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003558 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003559 'recovery_type': recovery_type,
3560 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003561 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003562
Bill Peckhame868aec2019-09-17 17:06:47 -07003563 # The install script location moved from /system/etc to /system/bin in the L
3564 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3565 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003566
Tao Bao32fcdab2018-10-12 10:30:39 -07003567 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003568
Tao Baoda30cfa2017-12-01 16:19:46 -08003569 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003570
3571
3572class DynamicPartitionUpdate(object):
3573 def __init__(self, src_group=None, tgt_group=None, progress=None,
3574 block_difference=None):
3575 self.src_group = src_group
3576 self.tgt_group = tgt_group
3577 self.progress = progress
3578 self.block_difference = block_difference
3579
3580 @property
3581 def src_size(self):
3582 if not self.block_difference:
3583 return 0
3584 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3585
3586 @property
3587 def tgt_size(self):
3588 if not self.block_difference:
3589 return 0
3590 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3591
3592 @staticmethod
3593 def _GetSparseImageSize(img):
3594 if not img:
3595 return 0
3596 return img.blocksize * img.total_blocks
3597
3598
3599class DynamicGroupUpdate(object):
3600 def __init__(self, src_size=None, tgt_size=None):
3601 # None: group does not exist. 0: no size limits.
3602 self.src_size = src_size
3603 self.tgt_size = tgt_size
3604
3605
3606class DynamicPartitionsDifference(object):
3607 def __init__(self, info_dict, block_diffs, progress_dict=None,
3608 source_info_dict=None):
3609 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003610 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003611
3612 self._remove_all_before_apply = False
3613 if source_info_dict is None:
3614 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003615 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003616
Tao Baof1113e92019-06-18 12:10:14 -07003617 block_diff_dict = collections.OrderedDict(
3618 [(e.partition, e) for e in block_diffs])
3619
Yifan Hong10c530d2018-12-27 17:34:18 -08003620 assert len(block_diff_dict) == len(block_diffs), \
3621 "Duplicated BlockDifference object for {}".format(
3622 [partition for partition, count in
3623 collections.Counter(e.partition for e in block_diffs).items()
3624 if count > 1])
3625
Yifan Hong79997e52019-01-23 16:56:19 -08003626 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003627
3628 for p, block_diff in block_diff_dict.items():
3629 self._partition_updates[p] = DynamicPartitionUpdate()
3630 self._partition_updates[p].block_difference = block_diff
3631
3632 for p, progress in progress_dict.items():
3633 if p in self._partition_updates:
3634 self._partition_updates[p].progress = progress
3635
3636 tgt_groups = shlex.split(info_dict.get(
3637 "super_partition_groups", "").strip())
3638 src_groups = shlex.split(source_info_dict.get(
3639 "super_partition_groups", "").strip())
3640
3641 for g in tgt_groups:
3642 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003643 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003644 assert p in self._partition_updates, \
3645 "{} is in target super_{}_partition_list but no BlockDifference " \
3646 "object is provided.".format(p, g)
3647 self._partition_updates[p].tgt_group = g
3648
3649 for g in src_groups:
3650 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003651 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003652 assert p in self._partition_updates, \
3653 "{} is in source super_{}_partition_list but no BlockDifference " \
3654 "object is provided.".format(p, g)
3655 self._partition_updates[p].src_group = g
3656
Yifan Hong45433e42019-01-18 13:55:25 -08003657 target_dynamic_partitions = set(shlex.split(info_dict.get(
3658 "dynamic_partition_list", "").strip()))
3659 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3660 if u.tgt_size)
3661 assert block_diffs_with_target == target_dynamic_partitions, \
3662 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3663 list(target_dynamic_partitions), list(block_diffs_with_target))
3664
3665 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3666 "dynamic_partition_list", "").strip()))
3667 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3668 if u.src_size)
3669 assert block_diffs_with_source == source_dynamic_partitions, \
3670 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3671 list(source_dynamic_partitions), list(block_diffs_with_source))
3672
Yifan Hong10c530d2018-12-27 17:34:18 -08003673 if self._partition_updates:
3674 logger.info("Updating dynamic partitions %s",
3675 self._partition_updates.keys())
3676
Yifan Hong79997e52019-01-23 16:56:19 -08003677 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003678
3679 for g in tgt_groups:
3680 self._group_updates[g] = DynamicGroupUpdate()
3681 self._group_updates[g].tgt_size = int(info_dict.get(
3682 "super_%s_group_size" % g, "0").strip())
3683
3684 for g in src_groups:
3685 if g not in self._group_updates:
3686 self._group_updates[g] = DynamicGroupUpdate()
3687 self._group_updates[g].src_size = int(source_info_dict.get(
3688 "super_%s_group_size" % g, "0").strip())
3689
3690 self._Compute()
3691
3692 def WriteScript(self, script, output_zip, write_verify_script=False):
3693 script.Comment('--- Start patching dynamic partitions ---')
3694 for p, u in self._partition_updates.items():
3695 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3696 script.Comment('Patch partition %s' % p)
3697 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3698 write_verify_script=False)
3699
3700 op_list_path = MakeTempFile()
3701 with open(op_list_path, 'w') as f:
3702 for line in self._op_list:
3703 f.write('{}\n'.format(line))
3704
3705 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3706
3707 script.Comment('Update dynamic partition metadata')
3708 script.AppendExtra('assert(update_dynamic_partitions('
3709 'package_extract_file("dynamic_partitions_op_list")));')
3710
3711 if write_verify_script:
3712 for p, u in self._partition_updates.items():
3713 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3714 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003715 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003716
3717 for p, u in self._partition_updates.items():
3718 if u.tgt_size and u.src_size <= u.tgt_size:
3719 script.Comment('Patch partition %s' % p)
3720 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3721 write_verify_script=write_verify_script)
3722 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003723 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003724
3725 script.Comment('--- End patching dynamic partitions ---')
3726
3727 def _Compute(self):
3728 self._op_list = list()
3729
3730 def append(line):
3731 self._op_list.append(line)
3732
3733 def comment(line):
3734 self._op_list.append("# %s" % line)
3735
3736 if self._remove_all_before_apply:
3737 comment('Remove all existing dynamic partitions and groups before '
3738 'applying full OTA')
3739 append('remove_all_groups')
3740
3741 for p, u in self._partition_updates.items():
3742 if u.src_group and not u.tgt_group:
3743 append('remove %s' % p)
3744
3745 for p, u in self._partition_updates.items():
3746 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3747 comment('Move partition %s from %s to default' % (p, u.src_group))
3748 append('move %s default' % p)
3749
3750 for p, u in self._partition_updates.items():
3751 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3752 comment('Shrink partition %s from %d to %d' %
3753 (p, u.src_size, u.tgt_size))
3754 append('resize %s %s' % (p, u.tgt_size))
3755
3756 for g, u in self._group_updates.items():
3757 if u.src_size is not None and u.tgt_size is None:
3758 append('remove_group %s' % g)
3759 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003760 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003761 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3762 append('resize_group %s %d' % (g, u.tgt_size))
3763
3764 for g, u in self._group_updates.items():
3765 if u.src_size is None and u.tgt_size is not None:
3766 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3767 append('add_group %s %d' % (g, u.tgt_size))
3768 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003769 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003770 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3771 append('resize_group %s %d' % (g, u.tgt_size))
3772
3773 for p, u in self._partition_updates.items():
3774 if u.tgt_group and not u.src_group:
3775 comment('Add partition %s to group %s' % (p, u.tgt_group))
3776 append('add %s %s' % (p, u.tgt_group))
3777
3778 for p, u in self._partition_updates.items():
3779 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003780 comment('Grow partition %s from %d to %d' %
3781 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003782 append('resize %s %d' % (p, u.tgt_size))
3783
3784 for p, u in self._partition_updates.items():
3785 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3786 comment('Move partition %s from default to %s' %
3787 (p, u.tgt_group))
3788 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003789
3790
jiajia tangf3f842b2021-03-17 21:49:44 +08003791def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003792 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003793 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003794
3795 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003796 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003797
3798 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003799 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003800 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003801 tmp_dir = MakeTempDir('boot_', suffix='.img')
3802 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003803 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3804 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003805 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3806 if not os.path.isfile(ramdisk):
3807 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3808 return None
3809 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003810 if ramdisk_format == RamdiskFormat.LZ4:
3811 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3812 elif ramdisk_format == RamdiskFormat.GZ:
3813 with open(ramdisk, 'rb') as input_stream:
3814 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003815 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3816 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003817 p2.wait()
3818 else:
3819 logger.error('Only support lz4 or minigzip ramdisk format.')
3820 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003821
3822 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3823 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3824 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3825 # the host environment.
3826 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003827 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003828
Yifan Hongc65a0542021-01-07 14:21:01 -08003829 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3830 prop_file = os.path.join(extracted_ramdisk, search_path)
3831 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003832 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003833 logger.warning(
3834 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003835
Yifan Hong7dc51172021-01-12 11:27:39 -08003836 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003837
Yifan Hong85ac5012021-01-07 14:43:46 -08003838 except ExternalError as e:
3839 logger.warning('Unable to get boot image build props: %s', e)
3840 return None
3841
3842
3843def GetBootImageTimestamp(boot_img):
3844 """
3845 Get timestamp from ramdisk within the boot image
3846
3847 Args:
3848 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3849
3850 Return:
3851 An integer that corresponds to the timestamp of the boot image, or None
3852 if file has unknown format. Raise exception if an unexpected error has
3853 occurred.
3854 """
3855 prop_file = GetBootImageBuildProp(boot_img)
3856 if not prop_file:
3857 return None
3858
3859 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3860 if props is None:
3861 return None
3862
3863 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003864 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3865 if timestamp:
3866 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003867 logger.warning(
3868 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003869 return None
3870
3871 except ExternalError as e:
3872 logger.warning('Unable to get boot image timestamp: %s', e)
3873 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003874
3875
3876def GetCareMap(which, imgname):
3877 """Returns the care_map string for the given partition.
3878
3879 Args:
3880 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3881 imgname: The filename of the image.
3882
3883 Returns:
3884 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3885 RangeSet; or None.
3886 """
3887 assert which in PARTITIONS_WITH_CARE_MAP
3888
3889 # which + "_image_size" contains the size that the actual filesystem image
3890 # resides in, which is all that needs to be verified. The additional blocks in
3891 # the image file contain verity metadata, by reading which would trigger
3892 # invalid reads.
3893 image_size = OPTIONS.info_dict.get(which + "_image_size")
3894 if not image_size:
3895 return None
3896
3897 image_blocks = int(image_size) // 4096 - 1
3898 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3899
3900 # For sparse images, we will only check the blocks that are listed in the care
3901 # map, i.e. the ones with meaningful data.
3902 if "extfs_sparse_flag" in OPTIONS.info_dict:
3903 simg = sparse_img.SparseImage(imgname)
3904 care_map_ranges = simg.care_map.intersect(
3905 rangelib.RangeSet("0-{}".format(image_blocks)))
3906
3907 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3908 # image.
3909 else:
3910 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3911
3912 return [which, care_map_ranges.to_string_raw()]
3913
3914
3915def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3916 """Generates and adds care_map.pb for a/b partition that has care_map.
3917
3918 Args:
3919 output_file: The output zip file (needs to be already open),
3920 or file path to write care_map.pb.
3921 ab_partitions: The list of A/B partitions.
3922 image_paths: A map from the partition name to the image path.
3923 """
3924 if not output_file:
3925 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3926
3927 care_map_list = []
3928 for partition in ab_partitions:
3929 partition = partition.strip()
3930 if partition not in PARTITIONS_WITH_CARE_MAP:
3931 continue
3932
3933 verity_block_device = "{}_verity_block_device".format(partition)
3934 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3935 if (verity_block_device in OPTIONS.info_dict or
3936 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3937 if partition not in image_paths:
3938 logger.warning('Potential partition with care_map missing from images: %s',
3939 partition)
3940 continue
3941 image_path = image_paths[partition]
3942 if not os.path.exists(image_path):
3943 raise ExternalError('Expected image at path {}'.format(image_path))
3944
3945 care_map = GetCareMap(partition, image_path)
3946 if not care_map:
3947 continue
3948 care_map_list += care_map
3949
3950 # adds fingerprint field to the care_map
3951 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3952 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3953 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3954 "ro.{}.build.thumbprint".format(partition)]
3955
3956 present_props = [x for x in prop_name_list if
3957 partition_props and partition_props.GetProp(x)]
3958 if not present_props:
3959 logger.warning(
3960 "fingerprint is not present for partition %s", partition)
3961 property_id, fingerprint = "unknown", "unknown"
3962 else:
3963 property_id = present_props[0]
3964 fingerprint = partition_props.GetProp(property_id)
3965 care_map_list += [property_id, fingerprint]
3966
3967 if not care_map_list:
3968 return
3969
3970 # Converts the list into proto buf message by calling care_map_generator; and
3971 # writes the result to a temp file.
3972 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3973 suffix=".txt")
3974 with open(temp_care_map_text, 'w') as text_file:
3975 text_file.write('\n'.join(care_map_list))
3976
3977 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3978 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3979 RunAndCheckOutput(care_map_gen_cmd)
3980
3981 if not isinstance(output_file, zipfile.ZipFile):
3982 shutil.copy(temp_care_map, output_file)
3983 return
3984 # output_file is a zip file
3985 care_map_path = "META/care_map.pb"
3986 if care_map_path in output_file.namelist():
3987 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3988 # replace_updated_files_list used by add_img_to_target_files
3989 if not OPTIONS.replace_updated_files_list:
3990 OPTIONS.replace_updated_files_list = []
3991 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3992 OPTIONS.replace_updated_files_list.append(care_map_path)
3993 else:
3994 ZipWrite(output_file, temp_care_map, arcname=care_map_path)