blob: 054795bbdd2c322a60f723863161cf5208d94ba3 [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
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hong5057b952021-01-07 14:09:57 -0800131# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800132PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800133
Yifan Hongc65a0542021-01-07 14:21:01 -0800134# See sysprop.mk. If file is moved, add new search paths here; don't remove
135# existing search paths.
136RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700137
Tianjie Xu209db462016-05-24 17:34:52 -0700138class ErrorCode(object):
139 """Define error_codes for failures that happen during the actual
140 update package installation.
141
142 Error codes 0-999 are reserved for failures before the package
143 installation (i.e. low battery, package verification failure).
144 Detailed code in 'bootable/recovery/error_code.h' """
145
146 SYSTEM_VERIFICATION_FAILURE = 1000
147 SYSTEM_UPDATE_FAILURE = 1001
148 SYSTEM_UNEXPECTED_CONTENTS = 1002
149 SYSTEM_NONZERO_CONTENTS = 1003
150 SYSTEM_RECOVER_FAILURE = 1004
151 VENDOR_VERIFICATION_FAILURE = 2000
152 VENDOR_UPDATE_FAILURE = 2001
153 VENDOR_UNEXPECTED_CONTENTS = 2002
154 VENDOR_NONZERO_CONTENTS = 2003
155 VENDOR_RECOVER_FAILURE = 2004
156 OEM_PROP_MISMATCH = 3000
157 FINGERPRINT_MISMATCH = 3001
158 THUMBPRINT_MISMATCH = 3002
159 OLDER_BUILD = 3003
160 DEVICE_MISMATCH = 3004
161 BAD_PATCH_FILE = 3005
162 INSUFFICIENT_CACHE_SPACE = 3006
163 TUNE_PARTITION_FAILURE = 3007
164 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800165
Tao Bao80921982018-03-21 21:02:19 -0700166
Dan Albert8b72aef2015-03-23 19:13:21 -0700167class ExternalError(RuntimeError):
168 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700169
170
Tao Bao32fcdab2018-10-12 10:30:39 -0700171def InitLogging():
172 DEFAULT_LOGGING_CONFIG = {
173 'version': 1,
174 'disable_existing_loggers': False,
175 'formatters': {
176 'standard': {
177 'format':
178 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
179 'datefmt': '%Y-%m-%d %H:%M:%S',
180 },
181 },
182 'handlers': {
183 'default': {
184 'class': 'logging.StreamHandler',
185 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700186 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 },
188 },
189 'loggers': {
190 '': {
191 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700193 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 }
195 }
196 }
197 env_config = os.getenv('LOGGING_CONFIG')
198 if env_config:
199 with open(env_config) as f:
200 config = json.load(f)
201 else:
202 config = DEFAULT_LOGGING_CONFIG
203
204 # Increase the logging level for verbose mode.
205 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700206 config = copy.deepcopy(config)
207 config['handlers']['default']['level'] = 'INFO'
208
209 if OPTIONS.logfile:
210 config = copy.deepcopy(config)
211 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400212 'class': 'logging.FileHandler',
213 'formatter': 'standard',
214 'level': 'INFO',
215 'mode': 'w',
216 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700217 }
218 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700219
220 logging.config.dictConfig(config)
221
222
Yifan Hong8e332ff2020-07-29 17:51:55 -0700223def SetHostToolLocation(tool_name, location):
224 OPTIONS.host_tools[tool_name] = location
225
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900226def FindHostToolPath(tool_name):
227 """Finds the path to the host tool.
228
229 Args:
230 tool_name: name of the tool to find
231 Returns:
232 path to the tool if found under either one of the host_tools map or under
233 the same directory as this binary is located at. If not found, tool_name
234 is returned.
235 """
236 if tool_name in OPTIONS.host_tools:
237 return OPTIONS.host_tools[tool_name]
238
239 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 tool_path = os.path.join(my_dir, tool_name)
241 if os.path.exists(tool_path):
242 return tool_path
243
244 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700245
Tao Bao39451582017-05-04 11:10:47 -0700246def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700248
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 Args:
250 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700251 verbose: Whether the commands should be shown. Default to the global
252 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
254 stdin, etc. stdout and stderr will default to subprocess.PIPE and
255 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800256 universal_newlines will default to True, as most of the users in
257 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700258
259 Returns:
260 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700261 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700262 if 'stdout' not in kwargs and 'stderr' not in kwargs:
263 kwargs['stdout'] = subprocess.PIPE
264 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800265 if 'universal_newlines' not in kwargs:
266 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700267
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900268 if args:
269 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900271 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272
Tao Bao32fcdab2018-10-12 10:30:39 -0700273 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400274 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700276 return subprocess.Popen(args, **kwargs)
277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800403 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800411 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800459
460 # Boot image uses ro.[product.]bootimage instead of boot.
461 prop_partition = "bootimage" if partition == "boot" else partition
462
Daniel Normand5fe8622020-01-08 17:01:11 -0800463 # If provided a partition for this property, only look within that
464 # partition's build.prop.
465 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800466 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800467 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800468 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000469
470 prop_val = self._GetRawBuildProp(prop, partition)
471 if prop_val is not None:
472 return prop_val
473 raise ExternalError("couldn't find %s in %s.build.prop" %
474 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800475
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800477 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
479 return self._ResolveRoProductBuildProp(prop)
480
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 prop_val = self._GetRawBuildProp(prop, None)
482 if prop_val is not None:
483 return prop_val
484
485 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700486
487 def _ResolveRoProductBuildProp(self, prop):
488 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700490 if prop_val:
491 return prop_val
492
Steven Laver8e2086e2020-04-27 16:26:31 -0700493 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 source_order_val = self._GetRawBuildProp(
495 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700496 if source_order_val:
497 source_order = source_order_val.split(",")
498 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700499 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700500
501 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700503 raise ExternalError(
504 "Invalid ro.product.property_source_order '{}'".format(source_order))
505
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000506 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700507 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 "ro.product", "ro.product.{}".format(source_partition), 1)
509 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700510 if prop_val:
511 return prop_val
512
513 raise ExternalError("couldn't resolve {}".format(prop))
514
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 def _GetRoProductPropsDefaultSourceOrder(self):
516 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
517 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000518 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700519 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if android_version == "10":
522 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
523 # NOTE: float() conversion of android_version will have rounding error.
524 # We are checking for "9" or less, and using "< 10" is well outside of
525 # possible floating point rounding.
526 try:
527 android_version_val = float(android_version)
528 except ValueError:
529 android_version_val = 0
530 if android_version_val < 10:
531 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
532 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
533
Tianjieb37c5be2020-10-15 21:27:10 -0700534 def _GetPlatformVersion(self):
535 version_sdk = self.GetBuildProp("ro.build.version.sdk")
536 # init code switches to version_release_or_codename (see b/158483506). After
537 # API finalization, release_or_codename will be the same as release. This
538 # is the best effort to support pre-S dev stage builds.
539 if int(version_sdk) >= 30:
540 try:
541 return self.GetBuildProp("ro.build.version.release_or_codename")
542 except ExternalError:
543 logger.warning('Failed to find ro.build.version.release_or_codename')
544
545 return self.GetBuildProp("ro.build.version.release")
546
547 def _GetPartitionPlatformVersion(self, partition):
548 try:
549 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
550 partition)
551 except ExternalError:
552 return self.GetPartitionBuildProp("ro.build.version.release",
553 partition)
554
Tao Bao1c320f82019-10-04 23:25:12 -0700555 def GetOemProperty(self, key):
556 if self.oem_props is not None and key in self.oem_props:
557 return self.oem_dicts[0][key]
558 return self.GetBuildProp(key)
559
Daniel Normand5fe8622020-01-08 17:01:11 -0800560 def GetPartitionFingerprint(self, partition):
561 return self._partition_fingerprints.get(partition, None)
562
563 def CalculatePartitionFingerprint(self, partition):
564 try:
565 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
566 except ExternalError:
567 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
568 self.GetPartitionBuildProp("ro.product.brand", partition),
569 self.GetPartitionBuildProp("ro.product.name", partition),
570 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700571 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800572 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400573 self.GetPartitionBuildProp(
574 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800575 self.GetPartitionBuildProp("ro.build.type", partition),
576 self.GetPartitionBuildProp("ro.build.tags", partition))
577
Tao Bao1c320f82019-10-04 23:25:12 -0700578 def CalculateFingerprint(self):
579 if self.oem_props is None:
580 try:
581 return self.GetBuildProp("ro.build.fingerprint")
582 except ExternalError:
583 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
584 self.GetBuildProp("ro.product.brand"),
585 self.GetBuildProp("ro.product.name"),
586 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700587 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700588 self.GetBuildProp("ro.build.id"),
589 self.GetBuildProp("ro.build.version.incremental"),
590 self.GetBuildProp("ro.build.type"),
591 self.GetBuildProp("ro.build.tags"))
592 return "%s/%s/%s:%s" % (
593 self.GetOemProperty("ro.product.brand"),
594 self.GetOemProperty("ro.product.name"),
595 self.GetOemProperty("ro.product.device"),
596 self.GetBuildProp("ro.build.thumbprint"))
597
598 def WriteMountOemScript(self, script):
599 assert self.oem_props is not None
600 recovery_mount_options = self.info_dict.get("recovery_mount_options")
601 script.Mount("/oem", recovery_mount_options)
602
603 def WriteDeviceAssertions(self, script, oem_no_mount):
604 # Read the property directly if not using OEM properties.
605 if not self.oem_props:
606 script.AssertDevice(self.device)
607 return
608
609 # Otherwise assert OEM properties.
610 if not self.oem_dicts:
611 raise ExternalError(
612 "No OEM file provided to answer expected assertions")
613
614 for prop in self.oem_props.split():
615 values = []
616 for oem_dict in self.oem_dicts:
617 if prop in oem_dict:
618 values.append(oem_dict[prop])
619 if not values:
620 raise ExternalError(
621 "The OEM file is missing the property %s" % (prop,))
622 script.AssertOemProperty(prop, values, oem_no_mount)
623
624
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000625def ReadFromInputFile(input_file, fn):
626 """Reads the contents of fn from input zipfile or directory."""
627 if isinstance(input_file, zipfile.ZipFile):
628 return input_file.read(fn).decode()
629 else:
630 path = os.path.join(input_file, *fn.split("/"))
631 try:
632 with open(path) as f:
633 return f.read()
634 except IOError as e:
635 if e.errno == errno.ENOENT:
636 raise KeyError(fn)
637
638
Yifan Hong10482a22021-01-07 14:38:41 -0800639def ExtractFromInputFile(input_file, fn):
640 """Extracts the contents of fn from input zipfile or directory into a file."""
641 if isinstance(input_file, zipfile.ZipFile):
642 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500643 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800644 f.write(input_file.read(fn))
645 return tmp_file
646 else:
647 file = os.path.join(input_file, *fn.split("/"))
648 if not os.path.exists(file):
649 raise KeyError(fn)
650 return file
651
652
Tao Bao410ad8b2018-08-24 12:08:38 -0700653def LoadInfoDict(input_file, repacking=False):
654 """Loads the key/value pairs from the given input target_files.
655
Tianjiea85bdf02020-07-29 11:56:19 -0700656 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700657 checks and returns the parsed key/value pairs for to the given build. It's
658 usually called early when working on input target_files files, e.g. when
659 generating OTAs, or signing builds. Note that the function may be called
660 against an old target_files file (i.e. from past dessert releases). So the
661 property parsing needs to be backward compatible.
662
663 In a `META/misc_info.txt`, a few properties are stored as links to the files
664 in the PRODUCT_OUT directory. It works fine with the build system. However,
665 they are no longer available when (re)generating images from target_files zip.
666 When `repacking` is True, redirect these properties to the actual files in the
667 unzipped directory.
668
669 Args:
670 input_file: The input target_files file, which could be an open
671 zipfile.ZipFile instance, or a str for the dir that contains the files
672 unzipped from a target_files file.
673 repacking: Whether it's trying repack an target_files file after loading the
674 info dict (default: False). If so, it will rewrite a few loaded
675 properties (e.g. selinux_fc, root_dir) to point to the actual files in
676 target_files file. When doing repacking, `input_file` must be a dir.
677
678 Returns:
679 A dict that contains the parsed key/value pairs.
680
681 Raises:
682 AssertionError: On invalid input arguments.
683 ValueError: On malformed input values.
684 """
685 if repacking:
686 assert isinstance(input_file, str), \
687 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700688
Doug Zongkerc9253822014-02-04 12:17:58 -0800689 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000690 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800691
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700692 try:
Michael Runge6e836112014-04-15 17:40:21 -0700693 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700694 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700695 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700696
Tao Bao410ad8b2018-08-24 12:08:38 -0700697 if "recovery_api_version" not in d:
698 raise ValueError("Failed to find 'recovery_api_version'")
699 if "fstab_version" not in d:
700 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800701
Tao Bao410ad8b2018-08-24 12:08:38 -0700702 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700703 # "selinux_fc" properties should point to the file_contexts files
704 # (file_contexts.bin) under META/.
705 for key in d:
706 if key.endswith("selinux_fc"):
707 fc_basename = os.path.basename(d[key])
708 fc_config = os.path.join(input_file, "META", fc_basename)
709 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700710
Daniel Norman72c626f2019-05-13 15:58:14 -0700711 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700712
Tom Cherryd14b8952018-08-09 14:26:00 -0700713 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700714 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700715 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700716 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700717
David Anderson0ec64ac2019-12-06 12:21:18 -0800718 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700719 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700720 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800721 key_name = part_name + "_base_fs_file"
722 if key_name not in d:
723 continue
724 basename = os.path.basename(d[key_name])
725 base_fs_file = os.path.join(input_file, "META", basename)
726 if os.path.exists(base_fs_file):
727 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700728 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700729 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800730 "Failed to find %s base fs file: %s", part_name, base_fs_file)
731 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700732
Doug Zongker37974732010-09-16 17:44:38 -0700733 def makeint(key):
734 if key in d:
735 d[key] = int(d[key], 0)
736
737 makeint("recovery_api_version")
738 makeint("blocksize")
739 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700740 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700741 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700742 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700743 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800744 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700745
Steve Muckle903a1ca2020-05-07 17:32:10 -0700746 boot_images = "boot.img"
747 if "boot_images" in d:
748 boot_images = d["boot_images"]
749 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400750 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700751
Tao Bao765668f2019-10-04 22:03:00 -0700752 # Load recovery fstab if applicable.
753 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800754
Tianjie Xu861f4132018-09-12 11:49:33 -0700755 # Tries to load the build props for all partitions with care_map, including
756 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800757 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800758 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000759 d[partition_prop] = PartitionBuildProps.FromInputFile(
760 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700761 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800762
Tao Bao3ed35d32019-10-07 20:48:48 -0700763 # Set up the salt (based on fingerprint) that will be used when adding AVB
764 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800765 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700766 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800767 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800768 fingerprint = build_info.GetPartitionFingerprint(partition)
769 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400770 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400771 try:
772 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
773 except KeyError:
774 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700775 return d
776
Tao Baod1de6f32017-03-01 16:38:48 -0800777
Kelvin Zhang39aea442020-08-17 11:04:25 -0400778
Daniel Norman4cc9df62019-07-18 10:11:07 -0700779def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900780 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700781 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900782
Daniel Norman4cc9df62019-07-18 10:11:07 -0700783
784def LoadDictionaryFromFile(file_path):
785 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900786 return LoadDictionaryFromLines(lines)
787
788
Michael Runge6e836112014-04-15 17:40:21 -0700789def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700790 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700791 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700792 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700793 if not line or line.startswith("#"):
794 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700795 if "=" in line:
796 name, value = line.split("=", 1)
797 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700798 return d
799
Tao Baod1de6f32017-03-01 16:38:48 -0800800
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000801class PartitionBuildProps(object):
802 """The class holds the build prop of a particular partition.
803
804 This class loads the build.prop and holds the build properties for a given
805 partition. It also partially recognizes the 'import' statement in the
806 build.prop; and calculates alternative values of some specific build
807 properties during runtime.
808
809 Attributes:
810 input_file: a zipped target-file or an unzipped target-file directory.
811 partition: name of the partition.
812 props_allow_override: a list of build properties to search for the
813 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000814 build_props: a dict of build properties for the given partition.
815 prop_overrides: a set of props that are overridden by import.
816 placeholder_values: A dict of runtime variables' values to replace the
817 placeholders in the build.prop file. We expect exactly one value for
818 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000819 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400820
Tianjie Xu9afb2212020-05-10 21:48:15 +0000821 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000822 self.input_file = input_file
823 self.partition = name
824 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000825 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000826 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 self.prop_overrides = set()
828 self.placeholder_values = {}
829 if placeholder_values:
830 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000831
832 @staticmethod
833 def FromDictionary(name, build_props):
834 """Constructs an instance from a build prop dictionary."""
835
836 props = PartitionBuildProps("unknown", name)
837 props.build_props = build_props.copy()
838 return props
839
840 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800843
844 if name == "boot":
845 data = PartitionBuildProps._ReadBootPropFile(input_file)
846 else:
847 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
848
849 props = PartitionBuildProps(input_file, name, placeholder_values)
850 props._LoadBuildProp(data)
851 return props
852
853 @staticmethod
854 def _ReadBootPropFile(input_file):
855 """
856 Read build.prop for boot image from input_file.
857 Return empty string if not found.
858 """
859 try:
860 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
861 except KeyError:
862 logger.warning('Failed to read IMAGES/boot.img')
863 return ''
864 prop_file = GetBootImageBuildProp(boot_img)
865 if prop_file is None:
866 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500867 with open(prop_file, "r") as f:
868 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800869
870 @staticmethod
871 def _ReadPartitionPropFile(input_file, name):
872 """
873 Read build.prop for name from input_file.
874 Return empty string if not found.
875 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000876 data = ''
877 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
878 '{}/build.prop'.format(name.upper())]:
879 try:
880 data = ReadFromInputFile(input_file, prop_file)
881 break
882 except KeyError:
883 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800884 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000885
Yifan Hong125d0b62020-09-24 17:07:03 -0700886 @staticmethod
887 def FromBuildPropFile(name, build_prop_file):
888 """Constructs an instance from a build prop file."""
889
890 props = PartitionBuildProps("unknown", name)
891 with open(build_prop_file) as f:
892 props._LoadBuildProp(f.read())
893 return props
894
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 def _LoadBuildProp(self, data):
896 for line in data.split('\n'):
897 line = line.strip()
898 if not line or line.startswith("#"):
899 continue
900 if line.startswith("import"):
901 overrides = self._ImportParser(line)
902 duplicates = self.prop_overrides.intersection(overrides.keys())
903 if duplicates:
904 raise ValueError('prop {} is overridden multiple times'.format(
905 ','.join(duplicates)))
906 self.prop_overrides = self.prop_overrides.union(overrides.keys())
907 self.build_props.update(overrides)
908 elif "=" in line:
909 name, value = line.split("=", 1)
910 if name in self.prop_overrides:
911 raise ValueError('prop {} is set again after overridden by import '
912 'statement'.format(name))
913 self.build_props[name] = value
914
915 def _ImportParser(self, line):
916 """Parses the build prop in a given import statement."""
917
918 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400919 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000920 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700921
922 if len(tokens) == 3:
923 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
924 return {}
925
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 import_path = tokens[1]
927 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
928 raise ValueError('Unrecognized import path {}'.format(line))
929
930 # We only recognize a subset of import statement that the init process
931 # supports. And we can loose the restriction based on how the dynamic
932 # fingerprint is used in practice. The placeholder format should be
933 # ${placeholder}, and its value should be provided by the caller through
934 # the placeholder_values.
935 for prop, value in self.placeholder_values.items():
936 prop_place_holder = '${{{}}}'.format(prop)
937 if prop_place_holder in import_path:
938 import_path = import_path.replace(prop_place_holder, value)
939 if '$' in import_path:
940 logger.info('Unresolved place holder in import path %s', import_path)
941 return {}
942
943 import_path = import_path.replace('/{}'.format(self.partition),
944 self.partition.upper())
945 logger.info('Parsing build props override from %s', import_path)
946
947 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
948 d = LoadDictionaryFromLines(lines)
949 return {key: val for key, val in d.items()
950 if key in self.props_allow_override}
951
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000952 def GetProp(self, prop):
953 return self.build_props.get(prop)
954
955
Tianjie Xucfa86222016-03-07 16:31:19 -0800956def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
957 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700958 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700959 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700960 self.mount_point = mount_point
961 self.fs_type = fs_type
962 self.device = device
963 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700964 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700965 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700966
967 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800968 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700969 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700970 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700971 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700972
Tao Baod1de6f32017-03-01 16:38:48 -0800973 assert fstab_version == 2
974
975 d = {}
976 for line in data.split("\n"):
977 line = line.strip()
978 if not line or line.startswith("#"):
979 continue
980
981 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
982 pieces = line.split()
983 if len(pieces) != 5:
984 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
985
986 # Ignore entries that are managed by vold.
987 options = pieces[4]
988 if "voldmanaged=" in options:
989 continue
990
991 # It's a good line, parse it.
992 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700993 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800994 options = options.split(",")
995 for i in options:
996 if i.startswith("length="):
997 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700998 elif i == "slotselect":
999 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001000 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001001 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001003
Tao Baod1de6f32017-03-01 16:38:48 -08001004 mount_flags = pieces[3]
1005 # Honor the SELinux context if present.
1006 context = None
1007 for i in mount_flags.split(","):
1008 if i.startswith("context="):
1009 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001010
Tao Baod1de6f32017-03-01 16:38:48 -08001011 mount_point = pieces[1]
1012 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001013 device=pieces[0], length=length, context=context,
1014 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001015
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001016 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001017 # system. Other areas assume system is always at "/system" so point /system
1018 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001019 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001020 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001021 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001022 return d
1023
1024
Tao Bao765668f2019-10-04 22:03:00 -07001025def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1026 """Finds the path to recovery fstab and loads its contents."""
1027 # recovery fstab is only meaningful when installing an update via recovery
1028 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001029 if info_dict.get('ab_update') == 'true' and \
1030 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001031 return None
1032
1033 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1034 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1035 # cases, since it may load the info_dict from an old build (e.g. when
1036 # generating incremental OTAs from that build).
1037 system_root_image = info_dict.get('system_root_image') == 'true'
1038 if info_dict.get('no_recovery') != 'true':
1039 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1040 if isinstance(input_file, zipfile.ZipFile):
1041 if recovery_fstab_path not in input_file.namelist():
1042 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1043 else:
1044 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1045 if not os.path.exists(path):
1046 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1047 return LoadRecoveryFSTab(
1048 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1049 system_root_image)
1050
1051 if info_dict.get('recovery_as_boot') == 'true':
1052 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1053 if isinstance(input_file, zipfile.ZipFile):
1054 if recovery_fstab_path not in input_file.namelist():
1055 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1056 else:
1057 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1058 if not os.path.exists(path):
1059 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1060 return LoadRecoveryFSTab(
1061 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1062 system_root_image)
1063
1064 return None
1065
1066
Doug Zongker37974732010-09-16 17:44:38 -07001067def DumpInfoDict(d):
1068 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001069 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001070
Dan Albert8b72aef2015-03-23 19:13:21 -07001071
Daniel Norman55417142019-11-25 16:04:36 -08001072def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001073 """Merges dynamic partition info variables.
1074
1075 Args:
1076 framework_dict: The dictionary of dynamic partition info variables from the
1077 partial framework target files.
1078 vendor_dict: The dictionary of dynamic partition info variables from the
1079 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001080
1081 Returns:
1082 The merged dynamic partition info dictionary.
1083 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001084
1085 def uniq_concat(a, b):
1086 combined = set(a.split(" "))
1087 combined.update(set(b.split(" ")))
1088 combined = [item.strip() for item in combined if item.strip()]
1089 return " ".join(sorted(combined))
1090
1091 if (framework_dict.get("use_dynamic_partitions") !=
1092 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1093 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1094
1095 merged_dict = {"use_dynamic_partitions": "true"}
1096
1097 merged_dict["dynamic_partition_list"] = uniq_concat(
1098 framework_dict.get("dynamic_partition_list", ""),
1099 vendor_dict.get("dynamic_partition_list", ""))
1100
1101 # Super block devices are defined by the vendor dict.
1102 if "super_block_devices" in vendor_dict:
1103 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1104 for block_device in merged_dict["super_block_devices"].split(" "):
1105 key = "super_%s_device_size" % block_device
1106 if key not in vendor_dict:
1107 raise ValueError("Vendor dict does not contain required key %s." % key)
1108 merged_dict[key] = vendor_dict[key]
1109
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001110 # Partition groups and group sizes are defined by the vendor dict because
1111 # these values may vary for each board that uses a shared system image.
1112 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001113 for partition_group in merged_dict["super_partition_groups"].split(" "):
1114 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001115 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001116 if key not in vendor_dict:
1117 raise ValueError("Vendor dict does not contain required key %s." % key)
1118 merged_dict[key] = vendor_dict[key]
1119
1120 # Set the partition group's partition list using a concatenation of the
1121 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001122 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001123 merged_dict[key] = uniq_concat(
1124 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301125
Daniel Normanb0c75912020-09-24 14:30:21 -07001126 # Various other flags should be copied from the vendor dict, if defined.
1127 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1128 "super_metadata_device", "super_partition_error_limit",
1129 "super_partition_size"):
1130 if key in vendor_dict.keys():
1131 merged_dict[key] = vendor_dict[key]
1132
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001133 return merged_dict
1134
1135
Daniel Norman21c34f72020-11-11 17:25:50 -08001136def PartitionMapFromTargetFiles(target_files_dir):
1137 """Builds a map from partition -> path within an extracted target files directory."""
1138 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1139 possible_subdirs = {
1140 "system": ["SYSTEM"],
1141 "vendor": ["VENDOR", "SYSTEM/vendor"],
1142 "product": ["PRODUCT", "SYSTEM/product"],
1143 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1144 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1145 "vendor_dlkm": [
1146 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1147 ],
1148 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1149 }
1150 partition_map = {}
1151 for partition, subdirs in possible_subdirs.items():
1152 for subdir in subdirs:
1153 if os.path.exists(os.path.join(target_files_dir, subdir)):
1154 partition_map[partition] = subdir
1155 break
1156 return partition_map
1157
1158
Daniel Normand3351562020-10-29 12:33:11 -07001159def SharedUidPartitionViolations(uid_dict, partition_groups):
1160 """Checks for APK sharedUserIds that cross partition group boundaries.
1161
1162 This uses a single or merged build's shareduid_violation_modules.json
1163 output file, as generated by find_shareduid_violation.py or
1164 core/tasks/find-shareduid-violation.mk.
1165
1166 An error is defined as a sharedUserId that is found in a set of partitions
1167 that span more than one partition group.
1168
1169 Args:
1170 uid_dict: A dictionary created by using the standard json module to read a
1171 complete shareduid_violation_modules.json file.
1172 partition_groups: A list of groups, where each group is a list of
1173 partitions.
1174
1175 Returns:
1176 A list of error messages.
1177 """
1178 errors = []
1179 for uid, partitions in uid_dict.items():
1180 found_in_groups = [
1181 group for group in partition_groups
1182 if set(partitions.keys()) & set(group)
1183 ]
1184 if len(found_in_groups) > 1:
1185 errors.append(
1186 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1187 % (uid, ",".join(sorted(partitions.keys()))))
1188 return errors
1189
1190
Daniel Norman21c34f72020-11-11 17:25:50 -08001191def RunHostInitVerifier(product_out, partition_map):
1192 """Runs host_init_verifier on the init rc files within partitions.
1193
1194 host_init_verifier searches the etc/init path within each partition.
1195
1196 Args:
1197 product_out: PRODUCT_OUT directory, containing partition directories.
1198 partition_map: A map of partition name -> relative path within product_out.
1199 """
1200 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1201 cmd = ["host_init_verifier"]
1202 for partition, path in partition_map.items():
1203 if partition not in allowed_partitions:
1204 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1205 partition)
1206 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1207 # Add --property-contexts if the file exists on the partition.
1208 property_contexts = "%s_property_contexts" % (
1209 "plat" if partition == "system" else partition)
1210 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1211 property_contexts)
1212 if os.path.exists(property_contexts_path):
1213 cmd.append("--property-contexts=%s" % property_contexts_path)
1214 # Add the passwd file if the file exists on the partition.
1215 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1216 if os.path.exists(passwd_path):
1217 cmd.extend(["-p", passwd_path])
1218 return RunAndCheckOutput(cmd)
1219
1220
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001221def AppendAVBSigningArgs(cmd, partition):
1222 """Append signing arguments for avbtool."""
1223 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1224 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001225 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1226 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1227 if os.path.exists(new_key_path):
1228 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001229 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1230 if key_path and algorithm:
1231 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001232 avb_salt = OPTIONS.info_dict.get("avb_salt")
1233 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001234 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001235 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001236
1237
Tao Bao765668f2019-10-04 22:03:00 -07001238def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001239 """Returns the VBMeta arguments for partition.
1240
1241 It sets up the VBMeta argument by including the partition descriptor from the
1242 given 'image', or by configuring the partition as a chained partition.
1243
1244 Args:
1245 partition: The name of the partition (e.g. "system").
1246 image: The path to the partition image.
1247 info_dict: A dict returned by common.LoadInfoDict(). Will use
1248 OPTIONS.info_dict if None has been given.
1249
1250 Returns:
1251 A list of VBMeta arguments.
1252 """
1253 if info_dict is None:
1254 info_dict = OPTIONS.info_dict
1255
1256 # Check if chain partition is used.
1257 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001258 if not key_path:
1259 return ["--include_descriptors_from_image", image]
1260
1261 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1262 # into vbmeta.img. The recovery image will be configured on an independent
1263 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1264 # See details at
1265 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001266 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001267 return []
1268
1269 # Otherwise chain the partition into vbmeta.
1270 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1271 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001272
1273
Tao Bao02a08592018-07-22 12:40:45 -07001274def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1275 """Constructs and returns the arg to build or verify a chained partition.
1276
1277 Args:
1278 partition: The partition name.
1279 info_dict: The info dict to look up the key info and rollback index
1280 location.
1281 key: The key to be used for building or verifying the partition. Defaults to
1282 the key listed in info_dict.
1283
1284 Returns:
1285 A string of form "partition:rollback_index_location:key" that can be used to
1286 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001287 """
1288 if key is None:
1289 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001290 if key and not os.path.exists(key) and OPTIONS.search_path:
1291 new_key_path = os.path.join(OPTIONS.search_path, key)
1292 if os.path.exists(new_key_path):
1293 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001294 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001295 rollback_index_location = info_dict[
1296 "avb_" + partition + "_rollback_index_location"]
1297 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1298
1299
Tianjie20dd8f22020-04-19 15:51:16 -07001300def ConstructAftlMakeImageCommands(output_image):
1301 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001302
1303 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001304 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001305 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1306 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1307 'No AFTL manufacturer key provided.'
1308
1309 vbmeta_image = MakeTempFile()
1310 os.rename(output_image, vbmeta_image)
1311 build_info = BuildInfo(OPTIONS.info_dict)
1312 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001313 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001314 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001315 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001316 "--vbmeta_image_path", vbmeta_image,
1317 "--output", output_image,
1318 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001319 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001320 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1321 "--algorithm", "SHA256_RSA4096",
1322 "--padding", "4096"]
1323 if OPTIONS.aftl_signer_helper:
1324 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001325 return aftl_cmd
1326
1327
1328def AddAftlInclusionProof(output_image):
1329 """Appends the aftl inclusion proof to the vbmeta image."""
1330
1331 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 RunAndCheckOutput(aftl_cmd)
1333
1334 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1335 output_image, '--transparency_log_pub_keys',
1336 OPTIONS.aftl_key_path]
1337 RunAndCheckOutput(verify_cmd)
1338
1339
Daniel Norman276f0622019-07-26 14:13:51 -07001340def BuildVBMeta(image_path, partitions, name, needed_partitions):
1341 """Creates a VBMeta image.
1342
1343 It generates the requested VBMeta image. The requested image could be for
1344 top-level or chained VBMeta image, which is determined based on the name.
1345
1346 Args:
1347 image_path: The output path for the new VBMeta image.
1348 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001349 values. Only valid partition names are accepted, as partitions listed
1350 in common.AVB_PARTITIONS and custom partitions listed in
1351 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001352 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1353 needed_partitions: Partitions whose descriptors should be included into the
1354 generated VBMeta image.
1355
1356 Raises:
1357 AssertionError: On invalid input args.
1358 """
1359 avbtool = OPTIONS.info_dict["avb_avbtool"]
1360 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1361 AppendAVBSigningArgs(cmd, name)
1362
Hongguang Chenf23364d2020-04-27 18:36:36 -07001363 custom_partitions = OPTIONS.info_dict.get(
1364 "avb_custom_images_partition_list", "").strip().split()
1365
Daniel Norman276f0622019-07-26 14:13:51 -07001366 for partition, path in partitions.items():
1367 if partition not in needed_partitions:
1368 continue
1369 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001370 partition in AVB_VBMETA_PARTITIONS or
1371 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001372 'Unknown partition: {}'.format(partition)
1373 assert os.path.exists(path), \
1374 'Failed to find {} for {}'.format(path, partition)
1375 cmd.extend(GetAvbPartitionArg(partition, path))
1376
1377 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1378 if args and args.strip():
1379 split_args = shlex.split(args)
1380 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001381 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001382 # as a path relative to source tree, which may not be available at the
1383 # same location when running this script (we have the input target_files
1384 # zip only). For such cases, we additionally scan other locations (e.g.
1385 # IMAGES/, RADIO/, etc) before bailing out.
1386 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001387 chained_image = split_args[index + 1]
1388 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001389 continue
1390 found = False
1391 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1392 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001393 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001394 if os.path.exists(alt_path):
1395 split_args[index + 1] = alt_path
1396 found = True
1397 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001398 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001399 cmd.extend(split_args)
1400
1401 RunAndCheckOutput(cmd)
1402
Tianjie Xueaed60c2020-03-12 00:33:28 -07001403 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001404 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001405 AddAftlInclusionProof(image_path)
1406
Daniel Norman276f0622019-07-26 14:13:51 -07001407
J. Avila98cd4cc2020-06-10 20:09:10 +00001408def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001409 ramdisk_img = tempfile.NamedTemporaryFile()
1410
1411 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1412 cmd = ["mkbootfs", "-f", fs_config_file,
1413 os.path.join(sourcedir, "RAMDISK")]
1414 else:
1415 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1416 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001417 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001418 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001419 stdout=ramdisk_img.file.fileno())
1420 else:
1421 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001422
1423 p2.wait()
1424 p1.wait()
1425 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001426 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001427
1428 return ramdisk_img
1429
1430
Steve Muckle9793cf62020-04-08 18:27:00 -07001431def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001432 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001433 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001434
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001435 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001436 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1437 we are building a two-step special image (i.e. building a recovery image to
1438 be loaded into /boot in two-step OTAs).
1439
1440 Return the image data, or None if sourcedir does not appear to contains files
1441 for building the requested image.
1442 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001443
Yifan Hong63c5ca12020-10-08 11:54:02 -07001444 if info_dict is None:
1445 info_dict = OPTIONS.info_dict
1446
Steve Muckle9793cf62020-04-08 18:27:00 -07001447 # "boot" or "recovery", without extension.
1448 partition_name = os.path.basename(sourcedir).lower()
1449
Yifan Hong63c5ca12020-10-08 11:54:02 -07001450 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001451 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001452 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1453 logger.info("Excluded kernel binary from recovery image.")
1454 else:
1455 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001456 else:
1457 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001458 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001459 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001460 return None
1461
1462 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001463 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001464
Doug Zongkereef39442009-04-02 12:14:19 -07001465 img = tempfile.NamedTemporaryFile()
1466
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001467 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001468 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1469 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001470
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001471 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1472 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1473
Yifan Hong63c5ca12020-10-08 11:54:02 -07001474 cmd = [mkbootimg]
1475 if kernel:
1476 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001477
Benoit Fradina45a8682014-07-14 21:00:43 +02001478 fn = os.path.join(sourcedir, "second")
1479 if os.access(fn, os.F_OK):
1480 cmd.append("--second")
1481 cmd.append(fn)
1482
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001483 fn = os.path.join(sourcedir, "dtb")
1484 if os.access(fn, os.F_OK):
1485 cmd.append("--dtb")
1486 cmd.append(fn)
1487
Doug Zongker171f1cd2009-06-15 22:36:37 -07001488 fn = os.path.join(sourcedir, "cmdline")
1489 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001490 cmd.append("--cmdline")
1491 cmd.append(open(fn).read().rstrip("\n"))
1492
1493 fn = os.path.join(sourcedir, "base")
1494 if os.access(fn, os.F_OK):
1495 cmd.append("--base")
1496 cmd.append(open(fn).read().rstrip("\n"))
1497
Ying Wang4de6b5b2010-08-25 14:29:34 -07001498 fn = os.path.join(sourcedir, "pagesize")
1499 if os.access(fn, os.F_OK):
1500 cmd.append("--pagesize")
1501 cmd.append(open(fn).read().rstrip("\n"))
1502
Steve Mucklef84668e2020-03-16 19:13:46 -07001503 if partition_name == "recovery":
1504 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301505 if not args:
1506 # Fall back to "mkbootimg_args" for recovery image
1507 # in case "recovery_mkbootimg_args" is not set.
1508 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001509 else:
1510 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001511 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001512 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001513
Tao Bao76def242017-11-21 09:25:31 -08001514 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001515 if args and args.strip():
1516 cmd.extend(shlex.split(args))
1517
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001518 if has_ramdisk:
1519 cmd.extend(["--ramdisk", ramdisk_img.name])
1520
Tao Baod95e9fd2015-03-29 23:07:41 -07001521 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001522 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001523 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001524 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001526 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001527
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001528 if partition_name == "recovery":
1529 if info_dict.get("include_recovery_dtbo") == "true":
1530 fn = os.path.join(sourcedir, "recovery_dtbo")
1531 cmd.extend(["--recovery_dtbo", fn])
1532 if info_dict.get("include_recovery_acpio") == "true":
1533 fn = os.path.join(sourcedir, "recovery_acpio")
1534 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001535
Tao Bao986ee862018-10-04 15:46:16 -07001536 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001537
Tao Bao76def242017-11-21 09:25:31 -08001538 if (info_dict.get("boot_signer") == "true" and
1539 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001540 # Hard-code the path as "/boot" for two-step special recovery image (which
1541 # will be loaded into /boot during the two-step OTA).
1542 if two_step_image:
1543 path = "/boot"
1544 else:
Tao Baobf70c312017-07-11 17:27:55 -07001545 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001546 cmd = [OPTIONS.boot_signer_path]
1547 cmd.extend(OPTIONS.boot_signer_args)
1548 cmd.extend([path, img.name,
1549 info_dict["verity_key"] + ".pk8",
1550 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001551 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001552
Tao Baod95e9fd2015-03-29 23:07:41 -07001553 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001554 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001555 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001556 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001557 # We have switched from the prebuilt futility binary to using the tool
1558 # (futility-host) built from the source. Override the setting in the old
1559 # TF.zip.
1560 futility = info_dict["futility"]
1561 if futility.startswith("prebuilts/"):
1562 futility = "futility-host"
1563 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001564 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001565 info_dict["vboot_key"] + ".vbprivk",
1566 info_dict["vboot_subkey"] + ".vbprivk",
1567 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001568 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001569 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001570
Tao Baof3282b42015-04-01 11:21:55 -07001571 # Clean up the temp files.
1572 img_unsigned.close()
1573 img_keyblock.close()
1574
David Zeuthen8fecb282017-12-01 16:24:01 -05001575 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001576 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001577 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001578 if partition_name == "recovery":
1579 part_size = info_dict["recovery_size"]
1580 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001581 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001582 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001583 "--partition_size", str(part_size), "--partition_name",
1584 partition_name]
1585 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001586 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001587 if args and args.strip():
1588 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001589 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001590
1591 img.seek(os.SEEK_SET, 0)
1592 data = img.read()
1593
1594 if has_ramdisk:
1595 ramdisk_img.close()
1596 img.close()
1597
1598 return data
1599
1600
Doug Zongkerd5131602012-08-02 14:46:42 -07001601def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001602 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001603 """Return a File object with the desired bootable image.
1604
1605 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1606 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1607 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001608
Doug Zongker55d93282011-01-25 17:03:34 -08001609 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1610 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001611 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001612 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001613
1614 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1615 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001616 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001617 return File.FromLocalFile(name, prebuilt_path)
1618
Tao Bao32fcdab2018-10-12 10:30:39 -07001619 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001620
1621 if info_dict is None:
1622 info_dict = OPTIONS.info_dict
1623
1624 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001625 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1626 # for recovery.
1627 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1628 prebuilt_name != "boot.img" or
1629 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001630
Doug Zongker6f1d0312014-08-22 08:07:12 -07001631 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001632 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001633 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001634 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001635 if data:
1636 return File(name, data)
1637 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001638
Doug Zongkereef39442009-04-02 12:14:19 -07001639
Steve Mucklee1b10862019-07-10 10:49:37 -07001640def _BuildVendorBootImage(sourcedir, info_dict=None):
1641 """Build a vendor boot image from the specified sourcedir.
1642
1643 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1644 turn them into a vendor boot image.
1645
1646 Return the image data, or None if sourcedir does not appear to contains files
1647 for building the requested image.
1648 """
1649
1650 if info_dict is None:
1651 info_dict = OPTIONS.info_dict
1652
1653 img = tempfile.NamedTemporaryFile()
1654
J. Avila98cd4cc2020-06-10 20:09:10 +00001655 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1656 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001657
1658 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1659 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1660
1661 cmd = [mkbootimg]
1662
1663 fn = os.path.join(sourcedir, "dtb")
1664 if os.access(fn, os.F_OK):
1665 cmd.append("--dtb")
1666 cmd.append(fn)
1667
1668 fn = os.path.join(sourcedir, "vendor_cmdline")
1669 if os.access(fn, os.F_OK):
1670 cmd.append("--vendor_cmdline")
1671 cmd.append(open(fn).read().rstrip("\n"))
1672
1673 fn = os.path.join(sourcedir, "base")
1674 if os.access(fn, os.F_OK):
1675 cmd.append("--base")
1676 cmd.append(open(fn).read().rstrip("\n"))
1677
1678 fn = os.path.join(sourcedir, "pagesize")
1679 if os.access(fn, os.F_OK):
1680 cmd.append("--pagesize")
1681 cmd.append(open(fn).read().rstrip("\n"))
1682
1683 args = info_dict.get("mkbootimg_args")
1684 if args and args.strip():
1685 cmd.extend(shlex.split(args))
1686
1687 args = info_dict.get("mkbootimg_version_args")
1688 if args and args.strip():
1689 cmd.extend(shlex.split(args))
1690
1691 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1692 cmd.extend(["--vendor_boot", img.name])
1693
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001694 ramdisk_fragment_imgs = []
1695 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1696 if os.access(fn, os.F_OK):
1697 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1698 for ramdisk_fragment in ramdisk_fragments:
1699 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1700 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1701 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1702 # Use prebuilt image if found, else create ramdisk from supplied files.
1703 if os.access(fn, os.F_OK):
1704 ramdisk_fragment_pathname = fn
1705 else:
1706 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1707 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1708 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1709 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1710 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1711
Steve Mucklee1b10862019-07-10 10:49:37 -07001712 RunAndCheckOutput(cmd)
1713
1714 # AVB: if enabled, calculate and add hash.
1715 if info_dict.get("avb_enable") == "true":
1716 avbtool = info_dict["avb_avbtool"]
1717 part_size = info_dict["vendor_boot_size"]
1718 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001719 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001720 AppendAVBSigningArgs(cmd, "vendor_boot")
1721 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1722 if args and args.strip():
1723 cmd.extend(shlex.split(args))
1724 RunAndCheckOutput(cmd)
1725
1726 img.seek(os.SEEK_SET, 0)
1727 data = img.read()
1728
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001729 for f in ramdisk_fragment_imgs:
1730 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001731 ramdisk_img.close()
1732 img.close()
1733
1734 return data
1735
1736
1737def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1738 info_dict=None):
1739 """Return a File object with the desired vendor boot image.
1740
1741 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1742 the source files in 'unpack_dir'/'tree_subdir'."""
1743
1744 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1745 if os.path.exists(prebuilt_path):
1746 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1747 return File.FromLocalFile(name, prebuilt_path)
1748
1749 logger.info("building image from target_files %s...", tree_subdir)
1750
1751 if info_dict is None:
1752 info_dict = OPTIONS.info_dict
1753
Kelvin Zhang0876c412020-06-23 15:06:58 -04001754 data = _BuildVendorBootImage(
1755 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001756 if data:
1757 return File(name, data)
1758 return None
1759
1760
Narayan Kamatha07bf042017-08-14 14:49:21 +01001761def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001762 """Gunzips the given gzip compressed file to a given output file."""
1763 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001764 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001765 shutil.copyfileobj(in_file, out_file)
1766
1767
Tao Bao0ff15de2019-03-20 11:26:06 -07001768def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001769 """Unzips the archive to the given directory.
1770
1771 Args:
1772 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001773 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001774 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1775 archvie. Non-matching patterns will be filtered out. If there's no match
1776 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001777 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001778 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001779 if patterns is not None:
1780 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001781 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001782 names = input_zip.namelist()
1783 filtered = [
1784 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1785
1786 # There isn't any matching files. Don't unzip anything.
1787 if not filtered:
1788 return
1789 cmd.extend(filtered)
1790
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001791 RunAndCheckOutput(cmd)
1792
1793
Doug Zongker75f17362009-12-08 13:46:44 -08001794def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001795 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001796
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001797 Args:
1798 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1799 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1800
1801 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1802 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001803
Tao Bao1c830bf2017-12-25 10:43:47 -08001804 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001805 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001806 """
Doug Zongkereef39442009-04-02 12:14:19 -07001807
Tao Bao1c830bf2017-12-25 10:43:47 -08001808 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001809 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1810 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001811 UnzipToDir(m.group(1), tmp, pattern)
1812 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001813 filename = m.group(1)
1814 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001815 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001816
Tao Baodba59ee2018-01-09 13:21:02 -08001817 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001818
1819
Yifan Hong8a66a712019-04-04 15:37:57 -07001820def GetUserImage(which, tmpdir, input_zip,
1821 info_dict=None,
1822 allow_shared_blocks=None,
1823 hashtree_info_generator=None,
1824 reset_file_map=False):
1825 """Returns an Image object suitable for passing to BlockImageDiff.
1826
1827 This function loads the specified image from the given path. If the specified
1828 image is sparse, it also performs additional processing for OTA purpose. For
1829 example, it always adds block 0 to clobbered blocks list. It also detects
1830 files that cannot be reconstructed from the block list, for whom we should
1831 avoid applying imgdiff.
1832
1833 Args:
1834 which: The partition name.
1835 tmpdir: The directory that contains the prebuilt image and block map file.
1836 input_zip: The target-files ZIP archive.
1837 info_dict: The dict to be looked up for relevant info.
1838 allow_shared_blocks: If image is sparse, whether having shared blocks is
1839 allowed. If none, it is looked up from info_dict.
1840 hashtree_info_generator: If present and image is sparse, generates the
1841 hashtree_info for this sparse image.
1842 reset_file_map: If true and image is sparse, reset file map before returning
1843 the image.
1844 Returns:
1845 A Image object. If it is a sparse image and reset_file_map is False, the
1846 image will have file_map info loaded.
1847 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001848 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001849 info_dict = LoadInfoDict(input_zip)
1850
1851 is_sparse = info_dict.get("extfs_sparse_flag")
1852
1853 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1854 # shared blocks (i.e. some blocks will show up in multiple files' block
1855 # list). We can only allocate such shared blocks to the first "owner", and
1856 # disable imgdiff for all later occurrences.
1857 if allow_shared_blocks is None:
1858 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1859
1860 if is_sparse:
1861 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1862 hashtree_info_generator)
1863 if reset_file_map:
1864 img.ResetFileMap()
1865 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001866 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001867
1868
1869def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1870 """Returns a Image object suitable for passing to BlockImageDiff.
1871
1872 This function loads the specified non-sparse image from the given path.
1873
1874 Args:
1875 which: The partition name.
1876 tmpdir: The directory that contains the prebuilt image and block map file.
1877 Returns:
1878 A Image object.
1879 """
1880 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1881 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1882
1883 # The image and map files must have been created prior to calling
1884 # ota_from_target_files.py (since LMP).
1885 assert os.path.exists(path) and os.path.exists(mappath)
1886
Tianjie Xu41976c72019-07-03 13:57:01 -07001887 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1888
Yifan Hong8a66a712019-04-04 15:37:57 -07001889
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001890def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1891 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001892 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1893
1894 This function loads the specified sparse image from the given path, and
1895 performs additional processing for OTA purpose. For example, it always adds
1896 block 0 to clobbered blocks list. It also detects files that cannot be
1897 reconstructed from the block list, for whom we should avoid applying imgdiff.
1898
1899 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001900 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001901 tmpdir: The directory that contains the prebuilt image and block map file.
1902 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001903 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001904 hashtree_info_generator: If present, generates the hashtree_info for this
1905 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001906 Returns:
1907 A SparseImage object, with file_map info loaded.
1908 """
Tao Baoc765cca2018-01-31 17:32:40 -08001909 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1910 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1911
1912 # The image and map files must have been created prior to calling
1913 # ota_from_target_files.py (since LMP).
1914 assert os.path.exists(path) and os.path.exists(mappath)
1915
1916 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1917 # it to clobbered_blocks so that it will be written to the target
1918 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1919 clobbered_blocks = "0"
1920
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001921 image = sparse_img.SparseImage(
1922 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1923 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001924
1925 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1926 # if they contain all zeros. We can't reconstruct such a file from its block
1927 # list. Tag such entries accordingly. (Bug: 65213616)
1928 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001929 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001930 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001931 continue
1932
Tom Cherryd14b8952018-08-09 14:26:00 -07001933 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1934 # filename listed in system.map may contain an additional leading slash
1935 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1936 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001937 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001938 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001939 arcname = entry.lstrip('/')
1940 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001941 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001942 else:
1943 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07001944
1945 assert arcname in input_zip.namelist(), \
1946 "Failed to find the ZIP entry for {}".format(entry)
1947
Tao Baoc765cca2018-01-31 17:32:40 -08001948 info = input_zip.getinfo(arcname)
1949 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001950
1951 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001952 # image, check the original block list to determine its completeness. Note
1953 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001954 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001955 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001956
Tao Baoc765cca2018-01-31 17:32:40 -08001957 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1958 ranges.extra['incomplete'] = True
1959
1960 return image
1961
1962
Doug Zongkereef39442009-04-02 12:14:19 -07001963def GetKeyPasswords(keylist):
1964 """Given a list of keys, prompt the user to enter passwords for
1965 those which require them. Return a {key: password} dict. password
1966 will be None if the key has no password."""
1967
Doug Zongker8ce7c252009-05-22 13:34:54 -07001968 no_passwords = []
1969 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001970 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001971 devnull = open("/dev/null", "w+b")
1972 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001973 # We don't need a password for things that aren't really keys.
1974 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001975 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001976 continue
1977
T.R. Fullhart37e10522013-03-18 10:31:26 -07001978 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001979 "-inform", "DER", "-nocrypt"],
1980 stdin=devnull.fileno(),
1981 stdout=devnull.fileno(),
1982 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001983 p.communicate()
1984 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001985 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001986 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001987 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001988 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1989 "-inform", "DER", "-passin", "pass:"],
1990 stdin=devnull.fileno(),
1991 stdout=devnull.fileno(),
1992 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001993 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001994 if p.returncode == 0:
1995 # Encrypted key with empty string as password.
1996 key_passwords[k] = ''
1997 elif stderr.startswith('Error decrypting key'):
1998 # Definitely encrypted key.
1999 # It would have said "Error reading key" if it didn't parse correctly.
2000 need_passwords.append(k)
2001 else:
2002 # Potentially, a type of key that openssl doesn't understand.
2003 # We'll let the routines in signapk.jar handle it.
2004 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002005 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002006
T.R. Fullhart37e10522013-03-18 10:31:26 -07002007 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002008 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002009 return key_passwords
2010
2011
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002012def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002013 """Gets the minSdkVersion declared in the APK.
2014
changho.shin0f125362019-07-08 10:59:00 +09002015 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002016 This can be both a decimal number (API Level) or a codename.
2017
2018 Args:
2019 apk_name: The APK filename.
2020
2021 Returns:
2022 The parsed SDK version string.
2023
2024 Raises:
2025 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002026 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002027 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002028 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002029 stderr=subprocess.PIPE)
2030 stdoutdata, stderrdata = proc.communicate()
2031 if proc.returncode != 0:
2032 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002033 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002034 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002035
Tao Baof47bf0f2018-03-21 23:28:51 -07002036 for line in stdoutdata.split("\n"):
2037 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002038 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2039 if m:
2040 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002041 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002042
2043
2044def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002045 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002046
Tao Baof47bf0f2018-03-21 23:28:51 -07002047 If minSdkVersion is set to a codename, it is translated to a number using the
2048 provided map.
2049
2050 Args:
2051 apk_name: The APK filename.
2052
2053 Returns:
2054 The parsed SDK version number.
2055
2056 Raises:
2057 ExternalError: On failing to get the min SDK version number.
2058 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002059 version = GetMinSdkVersion(apk_name)
2060 try:
2061 return int(version)
2062 except ValueError:
2063 # Not a decimal number. Codename?
2064 if version in codename_to_api_level_map:
2065 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002066 raise ExternalError(
2067 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2068 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002069
2070
2071def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002072 codename_to_api_level_map=None, whole_file=False,
2073 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002074 """Sign the input_name zip/jar/apk, producing output_name. Use the
2075 given key and password (the latter may be None if the key does not
2076 have a password.
2077
Doug Zongker951495f2009-08-14 12:44:19 -07002078 If whole_file is true, use the "-w" option to SignApk to embed a
2079 signature that covers the whole file in the archive comment of the
2080 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002081
2082 min_api_level is the API Level (int) of the oldest platform this file may end
2083 up on. If not specified for an APK, the API Level is obtained by interpreting
2084 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2085
2086 codename_to_api_level_map is needed to translate the codename which may be
2087 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002088
2089 Caller may optionally specify extra args to be passed to SignApk, which
2090 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002091 """
Tao Bao76def242017-11-21 09:25:31 -08002092 if codename_to_api_level_map is None:
2093 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002094 if extra_signapk_args is None:
2095 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002096
Alex Klyubin9667b182015-12-10 13:38:50 -08002097 java_library_path = os.path.join(
2098 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2099
Tao Baoe95540e2016-11-08 12:08:53 -08002100 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2101 ["-Djava.library.path=" + java_library_path,
2102 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002103 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002104 if whole_file:
2105 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002106
2107 min_sdk_version = min_api_level
2108 if min_sdk_version is None:
2109 if not whole_file:
2110 min_sdk_version = GetMinSdkVersionInt(
2111 input_name, codename_to_api_level_map)
2112 if min_sdk_version is not None:
2113 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2114
T.R. Fullhart37e10522013-03-18 10:31:26 -07002115 cmd.extend([key + OPTIONS.public_key_suffix,
2116 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002117 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002118
Tao Bao73dd4f42018-10-04 16:25:33 -07002119 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002120 if password is not None:
2121 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002122 stdoutdata, _ = proc.communicate(password)
2123 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002124 raise ExternalError(
2125 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002126 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002127
Doug Zongkereef39442009-04-02 12:14:19 -07002128
Doug Zongker37974732010-09-16 17:44:38 -07002129def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002130 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002131
Tao Bao9dd909e2017-11-14 11:27:32 -08002132 For non-AVB images, raise exception if the data is too big. Print a warning
2133 if the data is nearing the maximum size.
2134
2135 For AVB images, the actual image size should be identical to the limit.
2136
2137 Args:
2138 data: A string that contains all the data for the partition.
2139 target: The partition name. The ".img" suffix is optional.
2140 info_dict: The dict to be looked up for relevant info.
2141 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002142 if target.endswith(".img"):
2143 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002144 mount_point = "/" + target
2145
Ying Wangf8824af2014-06-03 14:07:27 -07002146 fs_type = None
2147 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002148 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002149 if mount_point == "/userdata":
2150 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002151 p = info_dict["fstab"][mount_point]
2152 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002153 device = p.device
2154 if "/" in device:
2155 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002156 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002157 if not fs_type or not limit:
2158 return
Doug Zongkereef39442009-04-02 12:14:19 -07002159
Andrew Boie0f9aec82012-02-14 09:32:52 -08002160 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002161 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2162 # path.
2163 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2164 if size != limit:
2165 raise ExternalError(
2166 "Mismatching image size for %s: expected %d actual %d" % (
2167 target, limit, size))
2168 else:
2169 pct = float(size) * 100.0 / limit
2170 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2171 if pct >= 99.0:
2172 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002173
2174 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002175 logger.warning("\n WARNING: %s\n", msg)
2176 else:
2177 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002178
2179
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002180def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002181 """Parses the APK certs info from a given target-files zip.
2182
2183 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2184 tuple with the following elements: (1) a dictionary that maps packages to
2185 certs (based on the "certificate" and "private_key" attributes in the file;
2186 (2) a string representing the extension of compressed APKs in the target files
2187 (e.g ".gz", ".bro").
2188
2189 Args:
2190 tf_zip: The input target_files ZipFile (already open).
2191
2192 Returns:
2193 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2194 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2195 no compressed APKs.
2196 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002197 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002198 compressed_extension = None
2199
Tao Bao0f990332017-09-08 19:02:54 -07002200 # META/apkcerts.txt contains the info for _all_ the packages known at build
2201 # time. Filter out the ones that are not installed.
2202 installed_files = set()
2203 for name in tf_zip.namelist():
2204 basename = os.path.basename(name)
2205 if basename:
2206 installed_files.add(basename)
2207
Tao Baoda30cfa2017-12-01 16:19:46 -08002208 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002209 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002210 if not line:
2211 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002212 m = re.match(
2213 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002214 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2215 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002216 line)
2217 if not m:
2218 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002219
Tao Bao818ddf52018-01-05 11:17:34 -08002220 matches = m.groupdict()
2221 cert = matches["CERT"]
2222 privkey = matches["PRIVKEY"]
2223 name = matches["NAME"]
2224 this_compressed_extension = matches["COMPRESSED"]
2225
2226 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2227 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2228 if cert in SPECIAL_CERT_STRINGS and not privkey:
2229 certmap[name] = cert
2230 elif (cert.endswith(OPTIONS.public_key_suffix) and
2231 privkey.endswith(OPTIONS.private_key_suffix) and
2232 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2233 certmap[name] = cert[:-public_key_suffix_len]
2234 else:
2235 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2236
2237 if not this_compressed_extension:
2238 continue
2239
2240 # Only count the installed files.
2241 filename = name + '.' + this_compressed_extension
2242 if filename not in installed_files:
2243 continue
2244
2245 # Make sure that all the values in the compression map have the same
2246 # extension. We don't support multiple compression methods in the same
2247 # system image.
2248 if compressed_extension:
2249 if this_compressed_extension != compressed_extension:
2250 raise ValueError(
2251 "Multiple compressed extensions: {} vs {}".format(
2252 compressed_extension, this_compressed_extension))
2253 else:
2254 compressed_extension = this_compressed_extension
2255
2256 return (certmap,
2257 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002258
2259
Doug Zongkereef39442009-04-02 12:14:19 -07002260COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002261Global options
2262
2263 -p (--path) <dir>
2264 Prepend <dir>/bin to the list of places to search for binaries run by this
2265 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002266
Doug Zongker05d3dea2009-06-22 11:32:31 -07002267 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002268 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002269
Tao Bao30df8b42018-04-23 15:32:53 -07002270 -x (--extra) <key=value>
2271 Add a key/value pair to the 'extras' dict, which device-specific extension
2272 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002273
Doug Zongkereef39442009-04-02 12:14:19 -07002274 -v (--verbose)
2275 Show command lines being executed.
2276
2277 -h (--help)
2278 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002279
2280 --logfile <file>
2281 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002282"""
2283
Kelvin Zhang0876c412020-06-23 15:06:58 -04002284
Doug Zongkereef39442009-04-02 12:14:19 -07002285def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002286 print(docstring.rstrip("\n"))
2287 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002288
2289
2290def ParseOptions(argv,
2291 docstring,
2292 extra_opts="", extra_long_opts=(),
2293 extra_option_handler=None):
2294 """Parse the options in argv and return any arguments that aren't
2295 flags. docstring is the calling module's docstring, to be displayed
2296 for errors and -h. extra_opts and extra_long_opts are for flags
2297 defined by the caller, which are processed by passing them to
2298 extra_option_handler."""
2299
2300 try:
2301 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002302 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002303 ["help", "verbose", "path=", "signapk_path=",
2304 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002305 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002306 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2307 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002308 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2309 "aftl_key_path=", "aftl_manufacturer_key_path=",
2310 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002311 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002312 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002313 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002314 sys.exit(2)
2315
Doug Zongkereef39442009-04-02 12:14:19 -07002316 for o, a in opts:
2317 if o in ("-h", "--help"):
2318 Usage(docstring)
2319 sys.exit()
2320 elif o in ("-v", "--verbose"):
2321 OPTIONS.verbose = True
2322 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002323 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002324 elif o in ("--signapk_path",):
2325 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002326 elif o in ("--signapk_shared_library_path",):
2327 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002328 elif o in ("--extra_signapk_args",):
2329 OPTIONS.extra_signapk_args = shlex.split(a)
2330 elif o in ("--java_path",):
2331 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002332 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002333 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002334 elif o in ("--android_jar_path",):
2335 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002336 elif o in ("--public_key_suffix",):
2337 OPTIONS.public_key_suffix = a
2338 elif o in ("--private_key_suffix",):
2339 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002340 elif o in ("--boot_signer_path",):
2341 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002342 elif o in ("--boot_signer_args",):
2343 OPTIONS.boot_signer_args = shlex.split(a)
2344 elif o in ("--verity_signer_path",):
2345 OPTIONS.verity_signer_path = a
2346 elif o in ("--verity_signer_args",):
2347 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002348 elif o in ("--aftl_tool_path",):
2349 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002350 elif o in ("--aftl_server",):
2351 OPTIONS.aftl_server = a
2352 elif o in ("--aftl_key_path",):
2353 OPTIONS.aftl_key_path = a
2354 elif o in ("--aftl_manufacturer_key_path",):
2355 OPTIONS.aftl_manufacturer_key_path = a
2356 elif o in ("--aftl_signer_helper",):
2357 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002358 elif o in ("-s", "--device_specific"):
2359 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002360 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002361 key, value = a.split("=", 1)
2362 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002363 elif o in ("--logfile",):
2364 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002365 else:
2366 if extra_option_handler is None or not extra_option_handler(o, a):
2367 assert False, "unknown option \"%s\"" % (o,)
2368
Doug Zongker85448772014-09-09 14:59:20 -07002369 if OPTIONS.search_path:
2370 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2371 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002372
2373 return args
2374
2375
Tao Bao4c851b12016-09-19 13:54:38 -07002376def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002377 """Make a temp file and add it to the list of things to be deleted
2378 when Cleanup() is called. Return the filename."""
2379 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2380 os.close(fd)
2381 OPTIONS.tempfiles.append(fn)
2382 return fn
2383
2384
Tao Bao1c830bf2017-12-25 10:43:47 -08002385def MakeTempDir(prefix='tmp', suffix=''):
2386 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2387
2388 Returns:
2389 The absolute pathname of the new directory.
2390 """
2391 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2392 OPTIONS.tempfiles.append(dir_name)
2393 return dir_name
2394
2395
Doug Zongkereef39442009-04-02 12:14:19 -07002396def Cleanup():
2397 for i in OPTIONS.tempfiles:
2398 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002399 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002400 else:
2401 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002402 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002403
2404
2405class PasswordManager(object):
2406 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002407 self.editor = os.getenv("EDITOR")
2408 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002409
2410 def GetPasswords(self, items):
2411 """Get passwords corresponding to each string in 'items',
2412 returning a dict. (The dict may have keys in addition to the
2413 values in 'items'.)
2414
2415 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2416 user edit that file to add more needed passwords. If no editor is
2417 available, or $ANDROID_PW_FILE isn't define, prompts the user
2418 interactively in the ordinary way.
2419 """
2420
2421 current = self.ReadFile()
2422
2423 first = True
2424 while True:
2425 missing = []
2426 for i in items:
2427 if i not in current or not current[i]:
2428 missing.append(i)
2429 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002430 if not missing:
2431 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002432
2433 for i in missing:
2434 current[i] = ""
2435
2436 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002437 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002438 if sys.version_info[0] >= 3:
2439 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002440 answer = raw_input("try to edit again? [y]> ").strip()
2441 if answer and answer[0] not in 'yY':
2442 raise RuntimeError("key passwords unavailable")
2443 first = False
2444
2445 current = self.UpdateAndReadFile(current)
2446
Kelvin Zhang0876c412020-06-23 15:06:58 -04002447 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002448 """Prompt the user to enter a value (password) for each key in
2449 'current' whose value is fales. Returns a new dict with all the
2450 values.
2451 """
2452 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002453 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002454 if v:
2455 result[k] = v
2456 else:
2457 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002458 result[k] = getpass.getpass(
2459 "Enter password for %s key> " % k).strip()
2460 if result[k]:
2461 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002462 return result
2463
2464 def UpdateAndReadFile(self, current):
2465 if not self.editor or not self.pwfile:
2466 return self.PromptResult(current)
2467
2468 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002469 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002470 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2471 f.write("# (Additional spaces are harmless.)\n\n")
2472
2473 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002474 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002475 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002476 f.write("[[[ %s ]]] %s\n" % (v, k))
2477 if not v and first_line is None:
2478 # position cursor on first line with no password.
2479 first_line = i + 4
2480 f.close()
2481
Tao Bao986ee862018-10-04 15:46:16 -07002482 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002483
2484 return self.ReadFile()
2485
2486 def ReadFile(self):
2487 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002488 if self.pwfile is None:
2489 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002490 try:
2491 f = open(self.pwfile, "r")
2492 for line in f:
2493 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002494 if not line or line[0] == '#':
2495 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002496 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2497 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002498 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002499 else:
2500 result[m.group(2)] = m.group(1)
2501 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002502 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002503 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002504 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002505 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002506
2507
Dan Albert8e0178d2015-01-27 15:53:15 -08002508def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2509 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002510
2511 # http://b/18015246
2512 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2513 # for files larger than 2GiB. We can work around this by adjusting their
2514 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2515 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2516 # it isn't clear to me exactly what circumstances cause this).
2517 # `zipfile.write()` must be used directly to work around this.
2518 #
2519 # This mess can be avoided if we port to python3.
2520 saved_zip64_limit = zipfile.ZIP64_LIMIT
2521 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2522
2523 if compress_type is None:
2524 compress_type = zip_file.compression
2525 if arcname is None:
2526 arcname = filename
2527
2528 saved_stat = os.stat(filename)
2529
2530 try:
2531 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2532 # file to be zipped and reset it when we're done.
2533 os.chmod(filename, perms)
2534
2535 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002536 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2537 # intentional. zip stores datetimes in local time without a time zone
2538 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2539 # in the zip archive.
2540 local_epoch = datetime.datetime.fromtimestamp(0)
2541 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002542 os.utime(filename, (timestamp, timestamp))
2543
2544 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2545 finally:
2546 os.chmod(filename, saved_stat.st_mode)
2547 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2548 zipfile.ZIP64_LIMIT = saved_zip64_limit
2549
2550
Tao Bao58c1b962015-05-20 09:32:18 -07002551def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002552 compress_type=None):
2553 """Wrap zipfile.writestr() function to work around the zip64 limit.
2554
2555 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2556 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2557 when calling crc32(bytes).
2558
2559 But it still works fine to write a shorter string into a large zip file.
2560 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2561 when we know the string won't be too long.
2562 """
2563
2564 saved_zip64_limit = zipfile.ZIP64_LIMIT
2565 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2566
2567 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2568 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002569 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002570 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002571 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002572 else:
Tao Baof3282b42015-04-01 11:21:55 -07002573 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002574 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2575 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2576 # such a case (since
2577 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2578 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2579 # permission bits. We follow the logic in Python 3 to get consistent
2580 # behavior between using the two versions.
2581 if not zinfo.external_attr:
2582 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002583
2584 # If compress_type is given, it overrides the value in zinfo.
2585 if compress_type is not None:
2586 zinfo.compress_type = compress_type
2587
Tao Bao58c1b962015-05-20 09:32:18 -07002588 # If perms is given, it has a priority.
2589 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002590 # If perms doesn't set the file type, mark it as a regular file.
2591 if perms & 0o770000 == 0:
2592 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002593 zinfo.external_attr = perms << 16
2594
Tao Baof3282b42015-04-01 11:21:55 -07002595 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002596 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2597
Dan Albert8b72aef2015-03-23 19:13:21 -07002598 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002599 zipfile.ZIP64_LIMIT = saved_zip64_limit
2600
2601
Tao Bao89d7ab22017-12-14 17:05:33 -08002602def ZipDelete(zip_filename, entries):
2603 """Deletes entries from a ZIP file.
2604
2605 Since deleting entries from a ZIP file is not supported, it shells out to
2606 'zip -d'.
2607
2608 Args:
2609 zip_filename: The name of the ZIP file.
2610 entries: The name of the entry, or the list of names to be deleted.
2611
2612 Raises:
2613 AssertionError: In case of non-zero return from 'zip'.
2614 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002615 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002616 entries = [entries]
2617 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002618 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002619
2620
Tao Baof3282b42015-04-01 11:21:55 -07002621def ZipClose(zip_file):
2622 # http://b/18015246
2623 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2624 # central directory.
2625 saved_zip64_limit = zipfile.ZIP64_LIMIT
2626 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2627
2628 zip_file.close()
2629
2630 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002631
2632
2633class DeviceSpecificParams(object):
2634 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002635
Doug Zongker05d3dea2009-06-22 11:32:31 -07002636 def __init__(self, **kwargs):
2637 """Keyword arguments to the constructor become attributes of this
2638 object, which is passed to all functions in the device-specific
2639 module."""
Tao Bao38884282019-07-10 22:20:56 -07002640 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002641 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002642 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002643
2644 if self.module is None:
2645 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002646 if not path:
2647 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002648 try:
2649 if os.path.isdir(path):
2650 info = imp.find_module("releasetools", [path])
2651 else:
2652 d, f = os.path.split(path)
2653 b, x = os.path.splitext(f)
2654 if x == ".py":
2655 f = b
2656 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002657 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002658 self.module = imp.load_module("device_specific", *info)
2659 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002660 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002661
2662 def _DoCall(self, function_name, *args, **kwargs):
2663 """Call the named function in the device-specific module, passing
2664 the given args and kwargs. The first argument to the call will be
2665 the DeviceSpecific object itself. If there is no module, or the
2666 module does not define the function, return the value of the
2667 'default' kwarg (which itself defaults to None)."""
2668 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002669 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002670 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2671
2672 def FullOTA_Assertions(self):
2673 """Called after emitting the block of assertions at the top of a
2674 full OTA package. Implementations can add whatever additional
2675 assertions they like."""
2676 return self._DoCall("FullOTA_Assertions")
2677
Doug Zongkere5ff5902012-01-17 10:55:37 -08002678 def FullOTA_InstallBegin(self):
2679 """Called at the start of full OTA installation."""
2680 return self._DoCall("FullOTA_InstallBegin")
2681
Yifan Hong10c530d2018-12-27 17:34:18 -08002682 def FullOTA_GetBlockDifferences(self):
2683 """Called during full OTA installation and verification.
2684 Implementation should return a list of BlockDifference objects describing
2685 the update on each additional partitions.
2686 """
2687 return self._DoCall("FullOTA_GetBlockDifferences")
2688
Doug Zongker05d3dea2009-06-22 11:32:31 -07002689 def FullOTA_InstallEnd(self):
2690 """Called at the end of full OTA installation; typically this is
2691 used to install the image for the device's baseband processor."""
2692 return self._DoCall("FullOTA_InstallEnd")
2693
2694 def IncrementalOTA_Assertions(self):
2695 """Called after emitting the block of assertions at the top of an
2696 incremental OTA package. Implementations can add whatever
2697 additional assertions they like."""
2698 return self._DoCall("IncrementalOTA_Assertions")
2699
Doug Zongkere5ff5902012-01-17 10:55:37 -08002700 def IncrementalOTA_VerifyBegin(self):
2701 """Called at the start of the verification phase of incremental
2702 OTA installation; additional checks can be placed here to abort
2703 the script before any changes are made."""
2704 return self._DoCall("IncrementalOTA_VerifyBegin")
2705
Doug Zongker05d3dea2009-06-22 11:32:31 -07002706 def IncrementalOTA_VerifyEnd(self):
2707 """Called at the end of the verification phase of incremental OTA
2708 installation; additional checks can be placed here to abort the
2709 script before any changes are made."""
2710 return self._DoCall("IncrementalOTA_VerifyEnd")
2711
Doug Zongkere5ff5902012-01-17 10:55:37 -08002712 def IncrementalOTA_InstallBegin(self):
2713 """Called at the start of incremental OTA installation (after
2714 verification is complete)."""
2715 return self._DoCall("IncrementalOTA_InstallBegin")
2716
Yifan Hong10c530d2018-12-27 17:34:18 -08002717 def IncrementalOTA_GetBlockDifferences(self):
2718 """Called during incremental OTA installation and verification.
2719 Implementation should return a list of BlockDifference objects describing
2720 the update on each additional partitions.
2721 """
2722 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2723
Doug Zongker05d3dea2009-06-22 11:32:31 -07002724 def IncrementalOTA_InstallEnd(self):
2725 """Called at the end of incremental OTA installation; typically
2726 this is used to install the image for the device's baseband
2727 processor."""
2728 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002729
Tao Bao9bc6bb22015-11-09 16:58:28 -08002730 def VerifyOTA_Assertions(self):
2731 return self._DoCall("VerifyOTA_Assertions")
2732
Tao Bao76def242017-11-21 09:25:31 -08002733
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002734class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002735 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002736 self.name = name
2737 self.data = data
2738 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002739 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002740 self.sha1 = sha1(data).hexdigest()
2741
2742 @classmethod
2743 def FromLocalFile(cls, name, diskname):
2744 f = open(diskname, "rb")
2745 data = f.read()
2746 f.close()
2747 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002748
2749 def WriteToTemp(self):
2750 t = tempfile.NamedTemporaryFile()
2751 t.write(self.data)
2752 t.flush()
2753 return t
2754
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002755 def WriteToDir(self, d):
2756 with open(os.path.join(d, self.name), "wb") as fp:
2757 fp.write(self.data)
2758
Geremy Condra36bd3652014-02-06 19:45:10 -08002759 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002760 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002761
Tao Bao76def242017-11-21 09:25:31 -08002762
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002763DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002764 ".gz": "imgdiff",
2765 ".zip": ["imgdiff", "-z"],
2766 ".jar": ["imgdiff", "-z"],
2767 ".apk": ["imgdiff", "-z"],
2768 ".img": "imgdiff",
2769}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002770
Tao Bao76def242017-11-21 09:25:31 -08002771
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002772class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002773 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002774 self.tf = tf
2775 self.sf = sf
2776 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002777 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002778
2779 def ComputePatch(self):
2780 """Compute the patch (as a string of data) needed to turn sf into
2781 tf. Returns the same tuple as GetPatch()."""
2782
2783 tf = self.tf
2784 sf = self.sf
2785
Doug Zongker24cd2802012-08-14 16:36:15 -07002786 if self.diff_program:
2787 diff_program = self.diff_program
2788 else:
2789 ext = os.path.splitext(tf.name)[1]
2790 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002791
2792 ttemp = tf.WriteToTemp()
2793 stemp = sf.WriteToTemp()
2794
2795 ext = os.path.splitext(tf.name)[1]
2796
2797 try:
2798 ptemp = tempfile.NamedTemporaryFile()
2799 if isinstance(diff_program, list):
2800 cmd = copy.copy(diff_program)
2801 else:
2802 cmd = [diff_program]
2803 cmd.append(stemp.name)
2804 cmd.append(ttemp.name)
2805 cmd.append(ptemp.name)
2806 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002807 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002808
Doug Zongkerf8340082014-08-05 10:39:37 -07002809 def run():
2810 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002811 if e:
2812 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002813 th = threading.Thread(target=run)
2814 th.start()
2815 th.join(timeout=300) # 5 mins
2816 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002817 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002818 p.terminate()
2819 th.join(5)
2820 if th.is_alive():
2821 p.kill()
2822 th.join()
2823
Tianjie Xua2a9f992018-01-05 15:15:54 -08002824 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002825 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002826 self.patch = None
2827 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002828 diff = ptemp.read()
2829 finally:
2830 ptemp.close()
2831 stemp.close()
2832 ttemp.close()
2833
2834 self.patch = diff
2835 return self.tf, self.sf, self.patch
2836
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002837 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002838 """Returns a tuple of (target_file, source_file, patch_data).
2839
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002840 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002841 computing the patch failed.
2842 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002843 return self.tf, self.sf, self.patch
2844
2845
2846def ComputeDifferences(diffs):
2847 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002848 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002849
2850 # Do the largest files first, to try and reduce the long-pole effect.
2851 by_size = [(i.tf.size, i) for i in diffs]
2852 by_size.sort(reverse=True)
2853 by_size = [i[1] for i in by_size]
2854
2855 lock = threading.Lock()
2856 diff_iter = iter(by_size) # accessed under lock
2857
2858 def worker():
2859 try:
2860 lock.acquire()
2861 for d in diff_iter:
2862 lock.release()
2863 start = time.time()
2864 d.ComputePatch()
2865 dur = time.time() - start
2866 lock.acquire()
2867
2868 tf, sf, patch = d.GetPatch()
2869 if sf.name == tf.name:
2870 name = tf.name
2871 else:
2872 name = "%s (%s)" % (tf.name, sf.name)
2873 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002874 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002875 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002876 logger.info(
2877 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2878 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002879 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002880 except Exception:
2881 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002882 raise
2883
2884 # start worker threads; wait for them all to finish.
2885 threads = [threading.Thread(target=worker)
2886 for i in range(OPTIONS.worker_threads)]
2887 for th in threads:
2888 th.start()
2889 while threads:
2890 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002891
2892
Dan Albert8b72aef2015-03-23 19:13:21 -07002893class BlockDifference(object):
2894 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002895 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002896 self.tgt = tgt
2897 self.src = src
2898 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002899 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002900 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002901
Tao Baodd2a5892015-03-12 12:32:37 -07002902 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002903 version = max(
2904 int(i) for i in
2905 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002906 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002907 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002908
Tianjie Xu41976c72019-07-03 13:57:01 -07002909 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2910 version=self.version,
2911 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002912 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002913 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002914 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002915 self.touched_src_ranges = b.touched_src_ranges
2916 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002917
Yifan Hong10c530d2018-12-27 17:34:18 -08002918 # On devices with dynamic partitions, for new partitions,
2919 # src is None but OPTIONS.source_info_dict is not.
2920 if OPTIONS.source_info_dict is None:
2921 is_dynamic_build = OPTIONS.info_dict.get(
2922 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002923 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002924 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002925 is_dynamic_build = OPTIONS.source_info_dict.get(
2926 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002927 is_dynamic_source = partition in shlex.split(
2928 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002929
Yifan Hongbb2658d2019-01-25 12:30:58 -08002930 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002931 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2932
Yifan Hongbb2658d2019-01-25 12:30:58 -08002933 # For dynamic partitions builds, check partition list in both source
2934 # and target build because new partitions may be added, and existing
2935 # partitions may be removed.
2936 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2937
Yifan Hong10c530d2018-12-27 17:34:18 -08002938 if is_dynamic:
2939 self.device = 'map_partition("%s")' % partition
2940 else:
2941 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002942 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2943 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002944 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002945 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2946 OPTIONS.source_info_dict)
2947 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002948
Tao Baod8d14be2016-02-04 14:26:02 -08002949 @property
2950 def required_cache(self):
2951 return self._required_cache
2952
Tao Bao76def242017-11-21 09:25:31 -08002953 def WriteScript(self, script, output_zip, progress=None,
2954 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002955 if not self.src:
2956 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002957 script.Print("Patching %s image unconditionally..." % (self.partition,))
2958 else:
2959 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002960
Dan Albert8b72aef2015-03-23 19:13:21 -07002961 if progress:
2962 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002963 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002964
2965 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002966 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002967
Tao Bao9bc6bb22015-11-09 16:58:28 -08002968 def WriteStrictVerifyScript(self, script):
2969 """Verify all the blocks in the care_map, including clobbered blocks.
2970
2971 This differs from the WriteVerifyScript() function: a) it prints different
2972 error messages; b) it doesn't allow half-way updated images to pass the
2973 verification."""
2974
2975 partition = self.partition
2976 script.Print("Verifying %s..." % (partition,))
2977 ranges = self.tgt.care_map
2978 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002979 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002980 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2981 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002982 self.device, ranges_str,
2983 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002984 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002985 script.AppendExtra("")
2986
Tao Baod522bdc2016-04-12 15:53:16 -07002987 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002988 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002989
2990 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002991 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002992 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002993
2994 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002995 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002996 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002997 ranges = self.touched_src_ranges
2998 expected_sha1 = self.touched_src_sha1
2999 else:
3000 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3001 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003002
3003 # No blocks to be checked, skipping.
3004 if not ranges:
3005 return
3006
Tao Bao5ece99d2015-05-12 11:42:31 -07003007 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003008 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003009 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003010 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3011 '"%s.patch.dat")) then' % (
3012 self.device, ranges_str, expected_sha1,
3013 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003014 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003015 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003016
Tianjie Xufc3422a2015-12-15 11:53:59 -08003017 if self.version >= 4:
3018
3019 # Bug: 21124327
3020 # When generating incrementals for the system and vendor partitions in
3021 # version 4 or newer, explicitly check the first block (which contains
3022 # the superblock) of the partition to see if it's what we expect. If
3023 # this check fails, give an explicit log message about the partition
3024 # having been remounted R/W (the most likely explanation).
3025 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003026 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003027
3028 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003029 if partition == "system":
3030 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3031 else:
3032 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003033 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003034 'ifelse (block_image_recover({device}, "{ranges}") && '
3035 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003036 'package_extract_file("{partition}.transfer.list"), '
3037 '"{partition}.new.dat", "{partition}.patch.dat"), '
3038 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003039 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003040 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003041 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003042
Tao Baodd2a5892015-03-12 12:32:37 -07003043 # Abort the OTA update. Note that the incremental OTA cannot be applied
3044 # even if it may match the checksum of the target partition.
3045 # a) If version < 3, operations like move and erase will make changes
3046 # unconditionally and damage the partition.
3047 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003048 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003049 if partition == "system":
3050 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3051 else:
3052 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3053 script.AppendExtra((
3054 'abort("E%d: %s partition has unexpected contents");\n'
3055 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003056
Yifan Hong10c530d2018-12-27 17:34:18 -08003057 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003058 partition = self.partition
3059 script.Print('Verifying the updated %s image...' % (partition,))
3060 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3061 ranges = self.tgt.care_map
3062 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003063 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003064 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003065 self.device, ranges_str,
3066 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003067
3068 # Bug: 20881595
3069 # Verify that extended blocks are really zeroed out.
3070 if self.tgt.extended:
3071 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003072 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003073 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003074 self.device, ranges_str,
3075 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003076 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003077 if partition == "system":
3078 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3079 else:
3080 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003081 script.AppendExtra(
3082 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003083 ' abort("E%d: %s partition has unexpected non-zero contents after '
3084 'OTA update");\n'
3085 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003086 else:
3087 script.Print('Verified the updated %s image.' % (partition,))
3088
Tianjie Xu209db462016-05-24 17:34:52 -07003089 if partition == "system":
3090 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3091 else:
3092 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3093
Tao Bao5fcaaef2015-06-01 13:40:49 -07003094 script.AppendExtra(
3095 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003096 ' abort("E%d: %s partition has unexpected contents after OTA '
3097 'update");\n'
3098 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003099
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003100 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003101 ZipWrite(output_zip,
3102 '{}.transfer.list'.format(self.path),
3103 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003104
Tao Bao76def242017-11-21 09:25:31 -08003105 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3106 # its size. Quailty 9 almost triples the compression time but doesn't
3107 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003108 # zip | brotli(quality 6) | brotli(quality 9)
3109 # compressed_size: 942M | 869M (~8% reduced) | 854M
3110 # compression_time: 75s | 265s | 719s
3111 # decompression_time: 15s | 25s | 25s
3112
3113 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003114 brotli_cmd = ['brotli', '--quality=6',
3115 '--output={}.new.dat.br'.format(self.path),
3116 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003117 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003118 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003119
3120 new_data_name = '{}.new.dat.br'.format(self.partition)
3121 ZipWrite(output_zip,
3122 '{}.new.dat.br'.format(self.path),
3123 new_data_name,
3124 compress_type=zipfile.ZIP_STORED)
3125 else:
3126 new_data_name = '{}.new.dat'.format(self.partition)
3127 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3128
Dan Albert8e0178d2015-01-27 15:53:15 -08003129 ZipWrite(output_zip,
3130 '{}.patch.dat'.format(self.path),
3131 '{}.patch.dat'.format(self.partition),
3132 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003133
Tianjie Xu209db462016-05-24 17:34:52 -07003134 if self.partition == "system":
3135 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3136 else:
3137 code = ErrorCode.VENDOR_UPDATE_FAILURE
3138
Yifan Hong10c530d2018-12-27 17:34:18 -08003139 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003140 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003141 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003142 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003143 device=self.device, partition=self.partition,
3144 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003145 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003146
Kelvin Zhang0876c412020-06-23 15:06:58 -04003147 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003148 data = source.ReadRangeSet(ranges)
3149 ctx = sha1()
3150
3151 for p in data:
3152 ctx.update(p)
3153
3154 return ctx.hexdigest()
3155
Kelvin Zhang0876c412020-06-23 15:06:58 -04003156 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003157 """Return the hash value for all zero blocks."""
3158 zero_block = '\x00' * 4096
3159 ctx = sha1()
3160 for _ in range(num_blocks):
3161 ctx.update(zero_block)
3162
3163 return ctx.hexdigest()
3164
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003165
Tianjie Xu41976c72019-07-03 13:57:01 -07003166# Expose these two classes to support vendor-specific scripts
3167DataImage = images.DataImage
3168EmptyImage = images.EmptyImage
3169
Tao Bao76def242017-11-21 09:25:31 -08003170
Doug Zongker96a57e72010-09-26 14:57:41 -07003171# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003172PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003173 "ext4": "EMMC",
3174 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003175 "f2fs": "EMMC",
3176 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003177}
Doug Zongker96a57e72010-09-26 14:57:41 -07003178
Kelvin Zhang0876c412020-06-23 15:06:58 -04003179
Yifan Hongbdb32012020-05-07 12:38:53 -07003180def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3181 """
3182 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3183 backwards compatibility. It aborts if the fstab entry has slotselect option
3184 (unless check_no_slot is explicitly set to False).
3185 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003186 fstab = info["fstab"]
3187 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003188 if check_no_slot:
3189 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003190 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003191 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3192 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003193 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003194
3195
Yifan Hongbdb32012020-05-07 12:38:53 -07003196def GetTypeAndDeviceExpr(mount_point, info):
3197 """
3198 Return the filesystem of the partition, and an edify expression that evaluates
3199 to the device at runtime.
3200 """
3201 fstab = info["fstab"]
3202 if fstab:
3203 p = fstab[mount_point]
3204 device_expr = '"%s"' % fstab[mount_point].device
3205 if p.slotselect:
3206 device_expr = 'add_slot_suffix(%s)' % device_expr
3207 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003208 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003209
3210
3211def GetEntryForDevice(fstab, device):
3212 """
3213 Returns:
3214 The first entry in fstab whose device is the given value.
3215 """
3216 if not fstab:
3217 return None
3218 for mount_point in fstab:
3219 if fstab[mount_point].device == device:
3220 return fstab[mount_point]
3221 return None
3222
Kelvin Zhang0876c412020-06-23 15:06:58 -04003223
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003224def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003225 """Parses and converts a PEM-encoded certificate into DER-encoded.
3226
3227 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3228
3229 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003230 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003231 """
3232 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003233 save = False
3234 for line in data.split("\n"):
3235 if "--END CERTIFICATE--" in line:
3236 break
3237 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003238 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003239 if "--BEGIN CERTIFICATE--" in line:
3240 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003241 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003242 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003243
Tao Bao04e1f012018-02-04 12:13:35 -08003244
3245def ExtractPublicKey(cert):
3246 """Extracts the public key (PEM-encoded) from the given certificate file.
3247
3248 Args:
3249 cert: The certificate filename.
3250
3251 Returns:
3252 The public key string.
3253
3254 Raises:
3255 AssertionError: On non-zero return from 'openssl'.
3256 """
3257 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3258 # While openssl 1.1 writes the key into the given filename followed by '-out',
3259 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3260 # stdout instead.
3261 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3262 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3263 pubkey, stderrdata = proc.communicate()
3264 assert proc.returncode == 0, \
3265 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3266 return pubkey
3267
3268
Tao Bao1ac886e2019-06-26 11:58:22 -07003269def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003270 """Extracts the AVB public key from the given public or private key.
3271
3272 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003273 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003274 key: The input key file, which should be PEM-encoded public or private key.
3275
3276 Returns:
3277 The path to the extracted AVB public key file.
3278 """
3279 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3280 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003281 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003282 return output
3283
3284
Doug Zongker412c02f2014-02-13 10:58:24 -08003285def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3286 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003287 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003288
Tao Bao6d5d6232018-03-09 17:04:42 -08003289 Most of the space in the boot and recovery images is just the kernel, which is
3290 identical for the two, so the resulting patch should be efficient. Add it to
3291 the output zip, along with a shell script that is run from init.rc on first
3292 boot to actually do the patching and install the new recovery image.
3293
3294 Args:
3295 input_dir: The top-level input directory of the target-files.zip.
3296 output_sink: The callback function that writes the result.
3297 recovery_img: File object for the recovery image.
3298 boot_img: File objects for the boot image.
3299 info_dict: A dict returned by common.LoadInfoDict() on the input
3300 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003301 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003302 if info_dict is None:
3303 info_dict = OPTIONS.info_dict
3304
Tao Bao6d5d6232018-03-09 17:04:42 -08003305 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003306 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3307
3308 if board_uses_vendorimage:
3309 # In this case, the output sink is rooted at VENDOR
3310 recovery_img_path = "etc/recovery.img"
3311 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3312 sh_dir = "bin"
3313 else:
3314 # In this case the output sink is rooted at SYSTEM
3315 recovery_img_path = "vendor/etc/recovery.img"
3316 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3317 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003318
Tao Baof2cffbd2015-07-22 12:33:18 -07003319 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003320 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003321
3322 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003323 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003324 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003325 # With system-root-image, boot and recovery images will have mismatching
3326 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3327 # to handle such a case.
3328 if system_root_image:
3329 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003330 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003331 assert not os.path.exists(path)
3332 else:
3333 diff_program = ["imgdiff"]
3334 if os.path.exists(path):
3335 diff_program.append("-b")
3336 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003337 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003338 else:
3339 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003340
3341 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3342 _, _, patch = d.ComputePatch()
3343 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003344
Dan Albertebb19aa2015-03-27 19:11:53 -07003345 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003346 # The following GetTypeAndDevice()s need to use the path in the target
3347 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003348 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3349 check_no_slot=False)
3350 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3351 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003352 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003353 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003354
Tao Baof2cffbd2015-07-22 12:33:18 -07003355 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003356
3357 # Note that we use /vendor to refer to the recovery resources. This will
3358 # work for a separate vendor partition mounted at /vendor or a
3359 # /system/vendor subdirectory on the system partition, for which init will
3360 # create a symlink from /vendor to /system/vendor.
3361
3362 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003363if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3364 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003365 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003366 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3367 log -t recovery "Installing new recovery image: succeeded" || \\
3368 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003369else
3370 log -t recovery "Recovery image already installed"
3371fi
3372""" % {'type': recovery_type,
3373 'device': recovery_device,
3374 'sha1': recovery_img.sha1,
3375 'size': recovery_img.size}
3376 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003377 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003378if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3379 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003380 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003381 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3382 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3383 log -t recovery "Installing new recovery image: succeeded" || \\
3384 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003385else
3386 log -t recovery "Recovery image already installed"
3387fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003388""" % {'boot_size': boot_img.size,
3389 'boot_sha1': boot_img.sha1,
3390 'recovery_size': recovery_img.size,
3391 'recovery_sha1': recovery_img.sha1,
3392 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003393 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003394 'recovery_type': recovery_type,
3395 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003396 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003397
Bill Peckhame868aec2019-09-17 17:06:47 -07003398 # The install script location moved from /system/etc to /system/bin in the L
3399 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3400 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003401
Tao Bao32fcdab2018-10-12 10:30:39 -07003402 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003403
Tao Baoda30cfa2017-12-01 16:19:46 -08003404 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003405
3406
3407class DynamicPartitionUpdate(object):
3408 def __init__(self, src_group=None, tgt_group=None, progress=None,
3409 block_difference=None):
3410 self.src_group = src_group
3411 self.tgt_group = tgt_group
3412 self.progress = progress
3413 self.block_difference = block_difference
3414
3415 @property
3416 def src_size(self):
3417 if not self.block_difference:
3418 return 0
3419 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3420
3421 @property
3422 def tgt_size(self):
3423 if not self.block_difference:
3424 return 0
3425 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3426
3427 @staticmethod
3428 def _GetSparseImageSize(img):
3429 if not img:
3430 return 0
3431 return img.blocksize * img.total_blocks
3432
3433
3434class DynamicGroupUpdate(object):
3435 def __init__(self, src_size=None, tgt_size=None):
3436 # None: group does not exist. 0: no size limits.
3437 self.src_size = src_size
3438 self.tgt_size = tgt_size
3439
3440
3441class DynamicPartitionsDifference(object):
3442 def __init__(self, info_dict, block_diffs, progress_dict=None,
3443 source_info_dict=None):
3444 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003445 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003446
3447 self._remove_all_before_apply = False
3448 if source_info_dict is None:
3449 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003450 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003451
Tao Baof1113e92019-06-18 12:10:14 -07003452 block_diff_dict = collections.OrderedDict(
3453 [(e.partition, e) for e in block_diffs])
3454
Yifan Hong10c530d2018-12-27 17:34:18 -08003455 assert len(block_diff_dict) == len(block_diffs), \
3456 "Duplicated BlockDifference object for {}".format(
3457 [partition for partition, count in
3458 collections.Counter(e.partition for e in block_diffs).items()
3459 if count > 1])
3460
Yifan Hong79997e52019-01-23 16:56:19 -08003461 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003462
3463 for p, block_diff in block_diff_dict.items():
3464 self._partition_updates[p] = DynamicPartitionUpdate()
3465 self._partition_updates[p].block_difference = block_diff
3466
3467 for p, progress in progress_dict.items():
3468 if p in self._partition_updates:
3469 self._partition_updates[p].progress = progress
3470
3471 tgt_groups = shlex.split(info_dict.get(
3472 "super_partition_groups", "").strip())
3473 src_groups = shlex.split(source_info_dict.get(
3474 "super_partition_groups", "").strip())
3475
3476 for g in tgt_groups:
3477 for p in shlex.split(info_dict.get(
3478 "super_%s_partition_list" % g, "").strip()):
3479 assert p in self._partition_updates, \
3480 "{} is in target super_{}_partition_list but no BlockDifference " \
3481 "object is provided.".format(p, g)
3482 self._partition_updates[p].tgt_group = g
3483
3484 for g in src_groups:
3485 for p in shlex.split(source_info_dict.get(
3486 "super_%s_partition_list" % g, "").strip()):
3487 assert p in self._partition_updates, \
3488 "{} is in source super_{}_partition_list but no BlockDifference " \
3489 "object is provided.".format(p, g)
3490 self._partition_updates[p].src_group = g
3491
Yifan Hong45433e42019-01-18 13:55:25 -08003492 target_dynamic_partitions = set(shlex.split(info_dict.get(
3493 "dynamic_partition_list", "").strip()))
3494 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3495 if u.tgt_size)
3496 assert block_diffs_with_target == target_dynamic_partitions, \
3497 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3498 list(target_dynamic_partitions), list(block_diffs_with_target))
3499
3500 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3501 "dynamic_partition_list", "").strip()))
3502 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3503 if u.src_size)
3504 assert block_diffs_with_source == source_dynamic_partitions, \
3505 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3506 list(source_dynamic_partitions), list(block_diffs_with_source))
3507
Yifan Hong10c530d2018-12-27 17:34:18 -08003508 if self._partition_updates:
3509 logger.info("Updating dynamic partitions %s",
3510 self._partition_updates.keys())
3511
Yifan Hong79997e52019-01-23 16:56:19 -08003512 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003513
3514 for g in tgt_groups:
3515 self._group_updates[g] = DynamicGroupUpdate()
3516 self._group_updates[g].tgt_size = int(info_dict.get(
3517 "super_%s_group_size" % g, "0").strip())
3518
3519 for g in src_groups:
3520 if g not in self._group_updates:
3521 self._group_updates[g] = DynamicGroupUpdate()
3522 self._group_updates[g].src_size = int(source_info_dict.get(
3523 "super_%s_group_size" % g, "0").strip())
3524
3525 self._Compute()
3526
3527 def WriteScript(self, script, output_zip, write_verify_script=False):
3528 script.Comment('--- Start patching dynamic partitions ---')
3529 for p, u in self._partition_updates.items():
3530 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3531 script.Comment('Patch partition %s' % p)
3532 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3533 write_verify_script=False)
3534
3535 op_list_path = MakeTempFile()
3536 with open(op_list_path, 'w') as f:
3537 for line in self._op_list:
3538 f.write('{}\n'.format(line))
3539
3540 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3541
3542 script.Comment('Update dynamic partition metadata')
3543 script.AppendExtra('assert(update_dynamic_partitions('
3544 'package_extract_file("dynamic_partitions_op_list")));')
3545
3546 if write_verify_script:
3547 for p, u in self._partition_updates.items():
3548 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3549 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003550 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003551
3552 for p, u in self._partition_updates.items():
3553 if u.tgt_size and u.src_size <= u.tgt_size:
3554 script.Comment('Patch partition %s' % p)
3555 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3556 write_verify_script=write_verify_script)
3557 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003558 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003559
3560 script.Comment('--- End patching dynamic partitions ---')
3561
3562 def _Compute(self):
3563 self._op_list = list()
3564
3565 def append(line):
3566 self._op_list.append(line)
3567
3568 def comment(line):
3569 self._op_list.append("# %s" % line)
3570
3571 if self._remove_all_before_apply:
3572 comment('Remove all existing dynamic partitions and groups before '
3573 'applying full OTA')
3574 append('remove_all_groups')
3575
3576 for p, u in self._partition_updates.items():
3577 if u.src_group and not u.tgt_group:
3578 append('remove %s' % p)
3579
3580 for p, u in self._partition_updates.items():
3581 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3582 comment('Move partition %s from %s to default' % (p, u.src_group))
3583 append('move %s default' % p)
3584
3585 for p, u in self._partition_updates.items():
3586 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3587 comment('Shrink partition %s from %d to %d' %
3588 (p, u.src_size, u.tgt_size))
3589 append('resize %s %s' % (p, u.tgt_size))
3590
3591 for g, u in self._group_updates.items():
3592 if u.src_size is not None and u.tgt_size is None:
3593 append('remove_group %s' % g)
3594 if (u.src_size is not None and u.tgt_size is not None and
3595 u.src_size > u.tgt_size):
3596 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3597 append('resize_group %s %d' % (g, u.tgt_size))
3598
3599 for g, u in self._group_updates.items():
3600 if u.src_size is None and u.tgt_size is not None:
3601 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3602 append('add_group %s %d' % (g, u.tgt_size))
3603 if (u.src_size is not None and u.tgt_size is not None and
3604 u.src_size < u.tgt_size):
3605 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3606 append('resize_group %s %d' % (g, u.tgt_size))
3607
3608 for p, u in self._partition_updates.items():
3609 if u.tgt_group and not u.src_group:
3610 comment('Add partition %s to group %s' % (p, u.tgt_group))
3611 append('add %s %s' % (p, u.tgt_group))
3612
3613 for p, u in self._partition_updates.items():
3614 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003615 comment('Grow partition %s from %d to %d' %
3616 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003617 append('resize %s %d' % (p, u.tgt_size))
3618
3619 for p, u in self._partition_updates.items():
3620 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3621 comment('Move partition %s from default to %s' %
3622 (p, u.tgt_group))
3623 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003624
3625
Yifan Hong85ac5012021-01-07 14:43:46 -08003626def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003627 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003628 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003629
3630 Args:
3631 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3632
3633 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003634 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003635 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003636 tmp_dir = MakeTempDir('boot_', suffix='.img')
3637 try:
3638 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3639 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3640 if not os.path.isfile(ramdisk):
3641 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3642 return None
3643 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3644 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3645
3646 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3647 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3648 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3649 # the host environment.
3650 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3651 cwd=extracted_ramdisk)
3652
Yifan Hongc65a0542021-01-07 14:21:01 -08003653 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3654 prop_file = os.path.join(extracted_ramdisk, search_path)
3655 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003656 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003657 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3658
Yifan Hong7dc51172021-01-12 11:27:39 -08003659 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003660
Yifan Hong85ac5012021-01-07 14:43:46 -08003661 except ExternalError as e:
3662 logger.warning('Unable to get boot image build props: %s', e)
3663 return None
3664
3665
3666def GetBootImageTimestamp(boot_img):
3667 """
3668 Get timestamp from ramdisk within the boot image
3669
3670 Args:
3671 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3672
3673 Return:
3674 An integer that corresponds to the timestamp of the boot image, or None
3675 if file has unknown format. Raise exception if an unexpected error has
3676 occurred.
3677 """
3678 prop_file = GetBootImageBuildProp(boot_img)
3679 if not prop_file:
3680 return None
3681
3682 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3683 if props is None:
3684 return None
3685
3686 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003687 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3688 if timestamp:
3689 return int(timestamp)
3690 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3691 return None
3692
3693 except ExternalError as e:
3694 logger.warning('Unable to get boot image timestamp: %s', e)
3695 return None