blob: fc1a6924d8f3e4925e303ab2e13cff638a924e9d [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.
Andrew Sculle077cf72021-02-18 10:27:29 +0000113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
114 'system', 'system_ext', 'vendor', 'vendor_boot',
115 'vendor_dlkm', '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
Devin Moore50509012021-01-13 10:45:04 -08001694 fn = os.path.join(sourcedir, "vendor_bootconfig")
1695 if os.access(fn, os.F_OK):
1696 cmd.append("--vendor_bootconfig")
1697 cmd.append(fn)
1698
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001699 ramdisk_fragment_imgs = []
1700 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1701 if os.access(fn, os.F_OK):
1702 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1703 for ramdisk_fragment in ramdisk_fragments:
1704 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1705 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1706 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1707 # Use prebuilt image if found, else create ramdisk from supplied files.
1708 if os.access(fn, os.F_OK):
1709 ramdisk_fragment_pathname = fn
1710 else:
1711 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1712 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1713 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1714 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1715 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1716
Steve Mucklee1b10862019-07-10 10:49:37 -07001717 RunAndCheckOutput(cmd)
1718
1719 # AVB: if enabled, calculate and add hash.
1720 if info_dict.get("avb_enable") == "true":
1721 avbtool = info_dict["avb_avbtool"]
1722 part_size = info_dict["vendor_boot_size"]
1723 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001724 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001725 AppendAVBSigningArgs(cmd, "vendor_boot")
1726 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1727 if args and args.strip():
1728 cmd.extend(shlex.split(args))
1729 RunAndCheckOutput(cmd)
1730
1731 img.seek(os.SEEK_SET, 0)
1732 data = img.read()
1733
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001734 for f in ramdisk_fragment_imgs:
1735 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001736 ramdisk_img.close()
1737 img.close()
1738
1739 return data
1740
1741
1742def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1743 info_dict=None):
1744 """Return a File object with the desired vendor boot image.
1745
1746 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1747 the source files in 'unpack_dir'/'tree_subdir'."""
1748
1749 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1750 if os.path.exists(prebuilt_path):
1751 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1752 return File.FromLocalFile(name, prebuilt_path)
1753
1754 logger.info("building image from target_files %s...", tree_subdir)
1755
1756 if info_dict is None:
1757 info_dict = OPTIONS.info_dict
1758
Kelvin Zhang0876c412020-06-23 15:06:58 -04001759 data = _BuildVendorBootImage(
1760 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001761 if data:
1762 return File(name, data)
1763 return None
1764
1765
Narayan Kamatha07bf042017-08-14 14:49:21 +01001766def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001767 """Gunzips the given gzip compressed file to a given output file."""
1768 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001769 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001770 shutil.copyfileobj(in_file, out_file)
1771
1772
Tao Bao0ff15de2019-03-20 11:26:06 -07001773def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001774 """Unzips the archive to the given directory.
1775
1776 Args:
1777 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001778 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001779 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1780 archvie. Non-matching patterns will be filtered out. If there's no match
1781 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001782 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001783 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001784 if patterns is not None:
1785 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001786 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001787 names = input_zip.namelist()
1788 filtered = [
1789 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1790
1791 # There isn't any matching files. Don't unzip anything.
1792 if not filtered:
1793 return
1794 cmd.extend(filtered)
1795
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001796 RunAndCheckOutput(cmd)
1797
1798
Doug Zongker75f17362009-12-08 13:46:44 -08001799def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001800 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001801
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001802 Args:
1803 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1804 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1805
1806 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1807 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001808
Tao Bao1c830bf2017-12-25 10:43:47 -08001809 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001810 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001811 """
Doug Zongkereef39442009-04-02 12:14:19 -07001812
Tao Bao1c830bf2017-12-25 10:43:47 -08001813 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001814 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1815 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001816 UnzipToDir(m.group(1), tmp, pattern)
1817 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001818 filename = m.group(1)
1819 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001820 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001821
Tao Baodba59ee2018-01-09 13:21:02 -08001822 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001823
1824
Yifan Hong8a66a712019-04-04 15:37:57 -07001825def GetUserImage(which, tmpdir, input_zip,
1826 info_dict=None,
1827 allow_shared_blocks=None,
1828 hashtree_info_generator=None,
1829 reset_file_map=False):
1830 """Returns an Image object suitable for passing to BlockImageDiff.
1831
1832 This function loads the specified image from the given path. If the specified
1833 image is sparse, it also performs additional processing for OTA purpose. For
1834 example, it always adds block 0 to clobbered blocks list. It also detects
1835 files that cannot be reconstructed from the block list, for whom we should
1836 avoid applying imgdiff.
1837
1838 Args:
1839 which: The partition name.
1840 tmpdir: The directory that contains the prebuilt image and block map file.
1841 input_zip: The target-files ZIP archive.
1842 info_dict: The dict to be looked up for relevant info.
1843 allow_shared_blocks: If image is sparse, whether having shared blocks is
1844 allowed. If none, it is looked up from info_dict.
1845 hashtree_info_generator: If present and image is sparse, generates the
1846 hashtree_info for this sparse image.
1847 reset_file_map: If true and image is sparse, reset file map before returning
1848 the image.
1849 Returns:
1850 A Image object. If it is a sparse image and reset_file_map is False, the
1851 image will have file_map info loaded.
1852 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001853 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001854 info_dict = LoadInfoDict(input_zip)
1855
1856 is_sparse = info_dict.get("extfs_sparse_flag")
1857
1858 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1859 # shared blocks (i.e. some blocks will show up in multiple files' block
1860 # list). We can only allocate such shared blocks to the first "owner", and
1861 # disable imgdiff for all later occurrences.
1862 if allow_shared_blocks is None:
1863 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1864
1865 if is_sparse:
1866 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1867 hashtree_info_generator)
1868 if reset_file_map:
1869 img.ResetFileMap()
1870 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001871 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001872
1873
1874def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1875 """Returns a Image object suitable for passing to BlockImageDiff.
1876
1877 This function loads the specified non-sparse image from the given path.
1878
1879 Args:
1880 which: The partition name.
1881 tmpdir: The directory that contains the prebuilt image and block map file.
1882 Returns:
1883 A Image object.
1884 """
1885 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1886 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1887
1888 # The image and map files must have been created prior to calling
1889 # ota_from_target_files.py (since LMP).
1890 assert os.path.exists(path) and os.path.exists(mappath)
1891
Tianjie Xu41976c72019-07-03 13:57:01 -07001892 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1893
Yifan Hong8a66a712019-04-04 15:37:57 -07001894
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001895def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1896 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001897 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1898
1899 This function loads the specified sparse image from the given path, and
1900 performs additional processing for OTA purpose. For example, it always adds
1901 block 0 to clobbered blocks list. It also detects files that cannot be
1902 reconstructed from the block list, for whom we should avoid applying imgdiff.
1903
1904 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001905 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001906 tmpdir: The directory that contains the prebuilt image and block map file.
1907 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001908 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001909 hashtree_info_generator: If present, generates the hashtree_info for this
1910 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001911 Returns:
1912 A SparseImage object, with file_map info loaded.
1913 """
Tao Baoc765cca2018-01-31 17:32:40 -08001914 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1915 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1916
1917 # The image and map files must have been created prior to calling
1918 # ota_from_target_files.py (since LMP).
1919 assert os.path.exists(path) and os.path.exists(mappath)
1920
1921 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1922 # it to clobbered_blocks so that it will be written to the target
1923 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1924 clobbered_blocks = "0"
1925
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001926 image = sparse_img.SparseImage(
1927 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1928 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001929
1930 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1931 # if they contain all zeros. We can't reconstruct such a file from its block
1932 # list. Tag such entries accordingly. (Bug: 65213616)
1933 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001934 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001935 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001936 continue
1937
Tom Cherryd14b8952018-08-09 14:26:00 -07001938 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1939 # filename listed in system.map may contain an additional leading slash
1940 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1941 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001942 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001943 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001944 arcname = entry.lstrip('/')
1945 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001946 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001947 else:
1948 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07001949
1950 assert arcname in input_zip.namelist(), \
1951 "Failed to find the ZIP entry for {}".format(entry)
1952
Tao Baoc765cca2018-01-31 17:32:40 -08001953 info = input_zip.getinfo(arcname)
1954 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001955
1956 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001957 # image, check the original block list to determine its completeness. Note
1958 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001959 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001960 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001961
Tao Baoc765cca2018-01-31 17:32:40 -08001962 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1963 ranges.extra['incomplete'] = True
1964
1965 return image
1966
1967
Doug Zongkereef39442009-04-02 12:14:19 -07001968def GetKeyPasswords(keylist):
1969 """Given a list of keys, prompt the user to enter passwords for
1970 those which require them. Return a {key: password} dict. password
1971 will be None if the key has no password."""
1972
Doug Zongker8ce7c252009-05-22 13:34:54 -07001973 no_passwords = []
1974 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001975 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001976 devnull = open("/dev/null", "w+b")
1977 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001978 # We don't need a password for things that aren't really keys.
1979 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001980 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001981 continue
1982
T.R. Fullhart37e10522013-03-18 10:31:26 -07001983 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001984 "-inform", "DER", "-nocrypt"],
1985 stdin=devnull.fileno(),
1986 stdout=devnull.fileno(),
1987 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001988 p.communicate()
1989 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001990 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001991 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001992 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001993 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1994 "-inform", "DER", "-passin", "pass:"],
1995 stdin=devnull.fileno(),
1996 stdout=devnull.fileno(),
1997 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001998 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001999 if p.returncode == 0:
2000 # Encrypted key with empty string as password.
2001 key_passwords[k] = ''
2002 elif stderr.startswith('Error decrypting key'):
2003 # Definitely encrypted key.
2004 # It would have said "Error reading key" if it didn't parse correctly.
2005 need_passwords.append(k)
2006 else:
2007 # Potentially, a type of key that openssl doesn't understand.
2008 # We'll let the routines in signapk.jar handle it.
2009 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002010 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002011
T.R. Fullhart37e10522013-03-18 10:31:26 -07002012 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002013 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002014 return key_passwords
2015
2016
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002017def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002018 """Gets the minSdkVersion declared in the APK.
2019
changho.shin0f125362019-07-08 10:59:00 +09002020 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002021 This can be both a decimal number (API Level) or a codename.
2022
2023 Args:
2024 apk_name: The APK filename.
2025
2026 Returns:
2027 The parsed SDK version string.
2028
2029 Raises:
2030 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002031 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002032 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002033 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002034 stderr=subprocess.PIPE)
2035 stdoutdata, stderrdata = proc.communicate()
2036 if proc.returncode != 0:
2037 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002038 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002039 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002040
Tao Baof47bf0f2018-03-21 23:28:51 -07002041 for line in stdoutdata.split("\n"):
2042 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002043 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2044 if m:
2045 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002046 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002047
2048
2049def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002050 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002051
Tao Baof47bf0f2018-03-21 23:28:51 -07002052 If minSdkVersion is set to a codename, it is translated to a number using the
2053 provided map.
2054
2055 Args:
2056 apk_name: The APK filename.
2057
2058 Returns:
2059 The parsed SDK version number.
2060
2061 Raises:
2062 ExternalError: On failing to get the min SDK version number.
2063 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002064 version = GetMinSdkVersion(apk_name)
2065 try:
2066 return int(version)
2067 except ValueError:
2068 # Not a decimal number. Codename?
2069 if version in codename_to_api_level_map:
2070 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002071 raise ExternalError(
2072 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2073 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002074
2075
2076def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002077 codename_to_api_level_map=None, whole_file=False,
2078 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002079 """Sign the input_name zip/jar/apk, producing output_name. Use the
2080 given key and password (the latter may be None if the key does not
2081 have a password.
2082
Doug Zongker951495f2009-08-14 12:44:19 -07002083 If whole_file is true, use the "-w" option to SignApk to embed a
2084 signature that covers the whole file in the archive comment of the
2085 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002086
2087 min_api_level is the API Level (int) of the oldest platform this file may end
2088 up on. If not specified for an APK, the API Level is obtained by interpreting
2089 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2090
2091 codename_to_api_level_map is needed to translate the codename which may be
2092 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002093
2094 Caller may optionally specify extra args to be passed to SignApk, which
2095 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002096 """
Tao Bao76def242017-11-21 09:25:31 -08002097 if codename_to_api_level_map is None:
2098 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002099 if extra_signapk_args is None:
2100 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002101
Alex Klyubin9667b182015-12-10 13:38:50 -08002102 java_library_path = os.path.join(
2103 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2104
Tao Baoe95540e2016-11-08 12:08:53 -08002105 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2106 ["-Djava.library.path=" + java_library_path,
2107 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002108 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002109 if whole_file:
2110 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002111
2112 min_sdk_version = min_api_level
2113 if min_sdk_version is None:
2114 if not whole_file:
2115 min_sdk_version = GetMinSdkVersionInt(
2116 input_name, codename_to_api_level_map)
2117 if min_sdk_version is not None:
2118 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2119
T.R. Fullhart37e10522013-03-18 10:31:26 -07002120 cmd.extend([key + OPTIONS.public_key_suffix,
2121 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002122 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002123
Tao Bao73dd4f42018-10-04 16:25:33 -07002124 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002125 if password is not None:
2126 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002127 stdoutdata, _ = proc.communicate(password)
2128 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002129 raise ExternalError(
2130 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002131 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002132
Doug Zongkereef39442009-04-02 12:14:19 -07002133
Doug Zongker37974732010-09-16 17:44:38 -07002134def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002135 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002136
Tao Bao9dd909e2017-11-14 11:27:32 -08002137 For non-AVB images, raise exception if the data is too big. Print a warning
2138 if the data is nearing the maximum size.
2139
2140 For AVB images, the actual image size should be identical to the limit.
2141
2142 Args:
2143 data: A string that contains all the data for the partition.
2144 target: The partition name. The ".img" suffix is optional.
2145 info_dict: The dict to be looked up for relevant info.
2146 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002147 if target.endswith(".img"):
2148 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002149 mount_point = "/" + target
2150
Ying Wangf8824af2014-06-03 14:07:27 -07002151 fs_type = None
2152 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002153 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002154 if mount_point == "/userdata":
2155 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002156 p = info_dict["fstab"][mount_point]
2157 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002158 device = p.device
2159 if "/" in device:
2160 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002161 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002162 if not fs_type or not limit:
2163 return
Doug Zongkereef39442009-04-02 12:14:19 -07002164
Andrew Boie0f9aec82012-02-14 09:32:52 -08002165 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002166 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2167 # path.
2168 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2169 if size != limit:
2170 raise ExternalError(
2171 "Mismatching image size for %s: expected %d actual %d" % (
2172 target, limit, size))
2173 else:
2174 pct = float(size) * 100.0 / limit
2175 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2176 if pct >= 99.0:
2177 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002178
2179 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002180 logger.warning("\n WARNING: %s\n", msg)
2181 else:
2182 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002183
2184
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002185def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002186 """Parses the APK certs info from a given target-files zip.
2187
2188 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2189 tuple with the following elements: (1) a dictionary that maps packages to
2190 certs (based on the "certificate" and "private_key" attributes in the file;
2191 (2) a string representing the extension of compressed APKs in the target files
2192 (e.g ".gz", ".bro").
2193
2194 Args:
2195 tf_zip: The input target_files ZipFile (already open).
2196
2197 Returns:
2198 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2199 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2200 no compressed APKs.
2201 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002202 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002203 compressed_extension = None
2204
Tao Bao0f990332017-09-08 19:02:54 -07002205 # META/apkcerts.txt contains the info for _all_ the packages known at build
2206 # time. Filter out the ones that are not installed.
2207 installed_files = set()
2208 for name in tf_zip.namelist():
2209 basename = os.path.basename(name)
2210 if basename:
2211 installed_files.add(basename)
2212
Tao Baoda30cfa2017-12-01 16:19:46 -08002213 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002214 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002215 if not line:
2216 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002217 m = re.match(
2218 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002219 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2220 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002221 line)
2222 if not m:
2223 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002224
Tao Bao818ddf52018-01-05 11:17:34 -08002225 matches = m.groupdict()
2226 cert = matches["CERT"]
2227 privkey = matches["PRIVKEY"]
2228 name = matches["NAME"]
2229 this_compressed_extension = matches["COMPRESSED"]
2230
2231 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2232 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2233 if cert in SPECIAL_CERT_STRINGS and not privkey:
2234 certmap[name] = cert
2235 elif (cert.endswith(OPTIONS.public_key_suffix) and
2236 privkey.endswith(OPTIONS.private_key_suffix) and
2237 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2238 certmap[name] = cert[:-public_key_suffix_len]
2239 else:
2240 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2241
2242 if not this_compressed_extension:
2243 continue
2244
2245 # Only count the installed files.
2246 filename = name + '.' + this_compressed_extension
2247 if filename not in installed_files:
2248 continue
2249
2250 # Make sure that all the values in the compression map have the same
2251 # extension. We don't support multiple compression methods in the same
2252 # system image.
2253 if compressed_extension:
2254 if this_compressed_extension != compressed_extension:
2255 raise ValueError(
2256 "Multiple compressed extensions: {} vs {}".format(
2257 compressed_extension, this_compressed_extension))
2258 else:
2259 compressed_extension = this_compressed_extension
2260
2261 return (certmap,
2262 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002263
2264
Doug Zongkereef39442009-04-02 12:14:19 -07002265COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002266Global options
2267
2268 -p (--path) <dir>
2269 Prepend <dir>/bin to the list of places to search for binaries run by this
2270 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002271
Doug Zongker05d3dea2009-06-22 11:32:31 -07002272 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002273 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002274
Tao Bao30df8b42018-04-23 15:32:53 -07002275 -x (--extra) <key=value>
2276 Add a key/value pair to the 'extras' dict, which device-specific extension
2277 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002278
Doug Zongkereef39442009-04-02 12:14:19 -07002279 -v (--verbose)
2280 Show command lines being executed.
2281
2282 -h (--help)
2283 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002284
2285 --logfile <file>
2286 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002287"""
2288
Kelvin Zhang0876c412020-06-23 15:06:58 -04002289
Doug Zongkereef39442009-04-02 12:14:19 -07002290def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002291 print(docstring.rstrip("\n"))
2292 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002293
2294
2295def ParseOptions(argv,
2296 docstring,
2297 extra_opts="", extra_long_opts=(),
2298 extra_option_handler=None):
2299 """Parse the options in argv and return any arguments that aren't
2300 flags. docstring is the calling module's docstring, to be displayed
2301 for errors and -h. extra_opts and extra_long_opts are for flags
2302 defined by the caller, which are processed by passing them to
2303 extra_option_handler."""
2304
2305 try:
2306 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002307 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002308 ["help", "verbose", "path=", "signapk_path=",
2309 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002310 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002311 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2312 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002313 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2314 "aftl_key_path=", "aftl_manufacturer_key_path=",
2315 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002316 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002317 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002318 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002319 sys.exit(2)
2320
Doug Zongkereef39442009-04-02 12:14:19 -07002321 for o, a in opts:
2322 if o in ("-h", "--help"):
2323 Usage(docstring)
2324 sys.exit()
2325 elif o in ("-v", "--verbose"):
2326 OPTIONS.verbose = True
2327 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002328 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002329 elif o in ("--signapk_path",):
2330 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002331 elif o in ("--signapk_shared_library_path",):
2332 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002333 elif o in ("--extra_signapk_args",):
2334 OPTIONS.extra_signapk_args = shlex.split(a)
2335 elif o in ("--java_path",):
2336 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002337 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002338 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002339 elif o in ("--android_jar_path",):
2340 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002341 elif o in ("--public_key_suffix",):
2342 OPTIONS.public_key_suffix = a
2343 elif o in ("--private_key_suffix",):
2344 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002345 elif o in ("--boot_signer_path",):
2346 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002347 elif o in ("--boot_signer_args",):
2348 OPTIONS.boot_signer_args = shlex.split(a)
2349 elif o in ("--verity_signer_path",):
2350 OPTIONS.verity_signer_path = a
2351 elif o in ("--verity_signer_args",):
2352 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002353 elif o in ("--aftl_tool_path",):
2354 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002355 elif o in ("--aftl_server",):
2356 OPTIONS.aftl_server = a
2357 elif o in ("--aftl_key_path",):
2358 OPTIONS.aftl_key_path = a
2359 elif o in ("--aftl_manufacturer_key_path",):
2360 OPTIONS.aftl_manufacturer_key_path = a
2361 elif o in ("--aftl_signer_helper",):
2362 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002363 elif o in ("-s", "--device_specific"):
2364 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002365 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002366 key, value = a.split("=", 1)
2367 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002368 elif o in ("--logfile",):
2369 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002370 else:
2371 if extra_option_handler is None or not extra_option_handler(o, a):
2372 assert False, "unknown option \"%s\"" % (o,)
2373
Doug Zongker85448772014-09-09 14:59:20 -07002374 if OPTIONS.search_path:
2375 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2376 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002377
2378 return args
2379
2380
Tao Bao4c851b12016-09-19 13:54:38 -07002381def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002382 """Make a temp file and add it to the list of things to be deleted
2383 when Cleanup() is called. Return the filename."""
2384 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2385 os.close(fd)
2386 OPTIONS.tempfiles.append(fn)
2387 return fn
2388
2389
Tao Bao1c830bf2017-12-25 10:43:47 -08002390def MakeTempDir(prefix='tmp', suffix=''):
2391 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2392
2393 Returns:
2394 The absolute pathname of the new directory.
2395 """
2396 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2397 OPTIONS.tempfiles.append(dir_name)
2398 return dir_name
2399
2400
Doug Zongkereef39442009-04-02 12:14:19 -07002401def Cleanup():
2402 for i in OPTIONS.tempfiles:
2403 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002404 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002405 else:
2406 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002407 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002408
2409
2410class PasswordManager(object):
2411 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002412 self.editor = os.getenv("EDITOR")
2413 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002414
2415 def GetPasswords(self, items):
2416 """Get passwords corresponding to each string in 'items',
2417 returning a dict. (The dict may have keys in addition to the
2418 values in 'items'.)
2419
2420 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2421 user edit that file to add more needed passwords. If no editor is
2422 available, or $ANDROID_PW_FILE isn't define, prompts the user
2423 interactively in the ordinary way.
2424 """
2425
2426 current = self.ReadFile()
2427
2428 first = True
2429 while True:
2430 missing = []
2431 for i in items:
2432 if i not in current or not current[i]:
2433 missing.append(i)
2434 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002435 if not missing:
2436 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002437
2438 for i in missing:
2439 current[i] = ""
2440
2441 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002442 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002443 if sys.version_info[0] >= 3:
2444 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002445 answer = raw_input("try to edit again? [y]> ").strip()
2446 if answer and answer[0] not in 'yY':
2447 raise RuntimeError("key passwords unavailable")
2448 first = False
2449
2450 current = self.UpdateAndReadFile(current)
2451
Kelvin Zhang0876c412020-06-23 15:06:58 -04002452 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002453 """Prompt the user to enter a value (password) for each key in
2454 'current' whose value is fales. Returns a new dict with all the
2455 values.
2456 """
2457 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002458 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002459 if v:
2460 result[k] = v
2461 else:
2462 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002463 result[k] = getpass.getpass(
2464 "Enter password for %s key> " % k).strip()
2465 if result[k]:
2466 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002467 return result
2468
2469 def UpdateAndReadFile(self, current):
2470 if not self.editor or not self.pwfile:
2471 return self.PromptResult(current)
2472
2473 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002474 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002475 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2476 f.write("# (Additional spaces are harmless.)\n\n")
2477
2478 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002479 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002480 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002481 f.write("[[[ %s ]]] %s\n" % (v, k))
2482 if not v and first_line is None:
2483 # position cursor on first line with no password.
2484 first_line = i + 4
2485 f.close()
2486
Tao Bao986ee862018-10-04 15:46:16 -07002487 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002488
2489 return self.ReadFile()
2490
2491 def ReadFile(self):
2492 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002493 if self.pwfile is None:
2494 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002495 try:
2496 f = open(self.pwfile, "r")
2497 for line in f:
2498 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002499 if not line or line[0] == '#':
2500 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002501 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2502 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002503 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002504 else:
2505 result[m.group(2)] = m.group(1)
2506 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002507 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002508 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002509 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002510 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002511
2512
Dan Albert8e0178d2015-01-27 15:53:15 -08002513def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2514 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002515
2516 # http://b/18015246
2517 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2518 # for files larger than 2GiB. We can work around this by adjusting their
2519 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2520 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2521 # it isn't clear to me exactly what circumstances cause this).
2522 # `zipfile.write()` must be used directly to work around this.
2523 #
2524 # This mess can be avoided if we port to python3.
2525 saved_zip64_limit = zipfile.ZIP64_LIMIT
2526 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2527
2528 if compress_type is None:
2529 compress_type = zip_file.compression
2530 if arcname is None:
2531 arcname = filename
2532
2533 saved_stat = os.stat(filename)
2534
2535 try:
2536 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2537 # file to be zipped and reset it when we're done.
2538 os.chmod(filename, perms)
2539
2540 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002541 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2542 # intentional. zip stores datetimes in local time without a time zone
2543 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2544 # in the zip archive.
2545 local_epoch = datetime.datetime.fromtimestamp(0)
2546 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002547 os.utime(filename, (timestamp, timestamp))
2548
2549 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2550 finally:
2551 os.chmod(filename, saved_stat.st_mode)
2552 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2553 zipfile.ZIP64_LIMIT = saved_zip64_limit
2554
2555
Tao Bao58c1b962015-05-20 09:32:18 -07002556def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002557 compress_type=None):
2558 """Wrap zipfile.writestr() function to work around the zip64 limit.
2559
2560 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2561 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2562 when calling crc32(bytes).
2563
2564 But it still works fine to write a shorter string into a large zip file.
2565 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2566 when we know the string won't be too long.
2567 """
2568
2569 saved_zip64_limit = zipfile.ZIP64_LIMIT
2570 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2571
2572 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2573 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002574 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002575 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002576 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002577 else:
Tao Baof3282b42015-04-01 11:21:55 -07002578 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002579 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2580 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2581 # such a case (since
2582 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2583 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2584 # permission bits. We follow the logic in Python 3 to get consistent
2585 # behavior between using the two versions.
2586 if not zinfo.external_attr:
2587 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002588
2589 # If compress_type is given, it overrides the value in zinfo.
2590 if compress_type is not None:
2591 zinfo.compress_type = compress_type
2592
Tao Bao58c1b962015-05-20 09:32:18 -07002593 # If perms is given, it has a priority.
2594 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002595 # If perms doesn't set the file type, mark it as a regular file.
2596 if perms & 0o770000 == 0:
2597 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002598 zinfo.external_attr = perms << 16
2599
Tao Baof3282b42015-04-01 11:21:55 -07002600 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002601 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2602
Dan Albert8b72aef2015-03-23 19:13:21 -07002603 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002604 zipfile.ZIP64_LIMIT = saved_zip64_limit
2605
2606
Tao Bao89d7ab22017-12-14 17:05:33 -08002607def ZipDelete(zip_filename, entries):
2608 """Deletes entries from a ZIP file.
2609
2610 Since deleting entries from a ZIP file is not supported, it shells out to
2611 'zip -d'.
2612
2613 Args:
2614 zip_filename: The name of the ZIP file.
2615 entries: The name of the entry, or the list of names to be deleted.
2616
2617 Raises:
2618 AssertionError: In case of non-zero return from 'zip'.
2619 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002620 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002621 entries = [entries]
2622 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002623 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002624
2625
Tao Baof3282b42015-04-01 11:21:55 -07002626def ZipClose(zip_file):
2627 # http://b/18015246
2628 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2629 # central directory.
2630 saved_zip64_limit = zipfile.ZIP64_LIMIT
2631 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2632
2633 zip_file.close()
2634
2635 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002636
2637
2638class DeviceSpecificParams(object):
2639 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002640
Doug Zongker05d3dea2009-06-22 11:32:31 -07002641 def __init__(self, **kwargs):
2642 """Keyword arguments to the constructor become attributes of this
2643 object, which is passed to all functions in the device-specific
2644 module."""
Tao Bao38884282019-07-10 22:20:56 -07002645 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002646 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002647 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002648
2649 if self.module is None:
2650 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002651 if not path:
2652 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002653 try:
2654 if os.path.isdir(path):
2655 info = imp.find_module("releasetools", [path])
2656 else:
2657 d, f = os.path.split(path)
2658 b, x = os.path.splitext(f)
2659 if x == ".py":
2660 f = b
2661 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002662 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002663 self.module = imp.load_module("device_specific", *info)
2664 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002665 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002666
2667 def _DoCall(self, function_name, *args, **kwargs):
2668 """Call the named function in the device-specific module, passing
2669 the given args and kwargs. The first argument to the call will be
2670 the DeviceSpecific object itself. If there is no module, or the
2671 module does not define the function, return the value of the
2672 'default' kwarg (which itself defaults to None)."""
2673 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002674 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002675 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2676
2677 def FullOTA_Assertions(self):
2678 """Called after emitting the block of assertions at the top of a
2679 full OTA package. Implementations can add whatever additional
2680 assertions they like."""
2681 return self._DoCall("FullOTA_Assertions")
2682
Doug Zongkere5ff5902012-01-17 10:55:37 -08002683 def FullOTA_InstallBegin(self):
2684 """Called at the start of full OTA installation."""
2685 return self._DoCall("FullOTA_InstallBegin")
2686
Yifan Hong10c530d2018-12-27 17:34:18 -08002687 def FullOTA_GetBlockDifferences(self):
2688 """Called during full OTA installation and verification.
2689 Implementation should return a list of BlockDifference objects describing
2690 the update on each additional partitions.
2691 """
2692 return self._DoCall("FullOTA_GetBlockDifferences")
2693
Doug Zongker05d3dea2009-06-22 11:32:31 -07002694 def FullOTA_InstallEnd(self):
2695 """Called at the end of full OTA installation; typically this is
2696 used to install the image for the device's baseband processor."""
2697 return self._DoCall("FullOTA_InstallEnd")
2698
2699 def IncrementalOTA_Assertions(self):
2700 """Called after emitting the block of assertions at the top of an
2701 incremental OTA package. Implementations can add whatever
2702 additional assertions they like."""
2703 return self._DoCall("IncrementalOTA_Assertions")
2704
Doug Zongkere5ff5902012-01-17 10:55:37 -08002705 def IncrementalOTA_VerifyBegin(self):
2706 """Called at the start of the verification phase of incremental
2707 OTA installation; additional checks can be placed here to abort
2708 the script before any changes are made."""
2709 return self._DoCall("IncrementalOTA_VerifyBegin")
2710
Doug Zongker05d3dea2009-06-22 11:32:31 -07002711 def IncrementalOTA_VerifyEnd(self):
2712 """Called at the end of the verification phase of incremental OTA
2713 installation; additional checks can be placed here to abort the
2714 script before any changes are made."""
2715 return self._DoCall("IncrementalOTA_VerifyEnd")
2716
Doug Zongkere5ff5902012-01-17 10:55:37 -08002717 def IncrementalOTA_InstallBegin(self):
2718 """Called at the start of incremental OTA installation (after
2719 verification is complete)."""
2720 return self._DoCall("IncrementalOTA_InstallBegin")
2721
Yifan Hong10c530d2018-12-27 17:34:18 -08002722 def IncrementalOTA_GetBlockDifferences(self):
2723 """Called during incremental OTA installation and verification.
2724 Implementation should return a list of BlockDifference objects describing
2725 the update on each additional partitions.
2726 """
2727 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2728
Doug Zongker05d3dea2009-06-22 11:32:31 -07002729 def IncrementalOTA_InstallEnd(self):
2730 """Called at the end of incremental OTA installation; typically
2731 this is used to install the image for the device's baseband
2732 processor."""
2733 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002734
Tao Bao9bc6bb22015-11-09 16:58:28 -08002735 def VerifyOTA_Assertions(self):
2736 return self._DoCall("VerifyOTA_Assertions")
2737
Tao Bao76def242017-11-21 09:25:31 -08002738
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002739class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002740 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002741 self.name = name
2742 self.data = data
2743 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002744 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002745 self.sha1 = sha1(data).hexdigest()
2746
2747 @classmethod
2748 def FromLocalFile(cls, name, diskname):
2749 f = open(diskname, "rb")
2750 data = f.read()
2751 f.close()
2752 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002753
2754 def WriteToTemp(self):
2755 t = tempfile.NamedTemporaryFile()
2756 t.write(self.data)
2757 t.flush()
2758 return t
2759
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002760 def WriteToDir(self, d):
2761 with open(os.path.join(d, self.name), "wb") as fp:
2762 fp.write(self.data)
2763
Geremy Condra36bd3652014-02-06 19:45:10 -08002764 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002765 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002766
Tao Bao76def242017-11-21 09:25:31 -08002767
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002768DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002769 ".gz": "imgdiff",
2770 ".zip": ["imgdiff", "-z"],
2771 ".jar": ["imgdiff", "-z"],
2772 ".apk": ["imgdiff", "-z"],
2773 ".img": "imgdiff",
2774}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002775
Tao Bao76def242017-11-21 09:25:31 -08002776
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002777class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002778 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002779 self.tf = tf
2780 self.sf = sf
2781 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002782 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002783
2784 def ComputePatch(self):
2785 """Compute the patch (as a string of data) needed to turn sf into
2786 tf. Returns the same tuple as GetPatch()."""
2787
2788 tf = self.tf
2789 sf = self.sf
2790
Doug Zongker24cd2802012-08-14 16:36:15 -07002791 if self.diff_program:
2792 diff_program = self.diff_program
2793 else:
2794 ext = os.path.splitext(tf.name)[1]
2795 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002796
2797 ttemp = tf.WriteToTemp()
2798 stemp = sf.WriteToTemp()
2799
2800 ext = os.path.splitext(tf.name)[1]
2801
2802 try:
2803 ptemp = tempfile.NamedTemporaryFile()
2804 if isinstance(diff_program, list):
2805 cmd = copy.copy(diff_program)
2806 else:
2807 cmd = [diff_program]
2808 cmd.append(stemp.name)
2809 cmd.append(ttemp.name)
2810 cmd.append(ptemp.name)
2811 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002812 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002813
Doug Zongkerf8340082014-08-05 10:39:37 -07002814 def run():
2815 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002816 if e:
2817 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002818 th = threading.Thread(target=run)
2819 th.start()
2820 th.join(timeout=300) # 5 mins
2821 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002822 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002823 p.terminate()
2824 th.join(5)
2825 if th.is_alive():
2826 p.kill()
2827 th.join()
2828
Tianjie Xua2a9f992018-01-05 15:15:54 -08002829 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002830 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002831 self.patch = None
2832 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002833 diff = ptemp.read()
2834 finally:
2835 ptemp.close()
2836 stemp.close()
2837 ttemp.close()
2838
2839 self.patch = diff
2840 return self.tf, self.sf, self.patch
2841
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002842 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002843 """Returns a tuple of (target_file, source_file, patch_data).
2844
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002845 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002846 computing the patch failed.
2847 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002848 return self.tf, self.sf, self.patch
2849
2850
2851def ComputeDifferences(diffs):
2852 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002853 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002854
2855 # Do the largest files first, to try and reduce the long-pole effect.
2856 by_size = [(i.tf.size, i) for i in diffs]
2857 by_size.sort(reverse=True)
2858 by_size = [i[1] for i in by_size]
2859
2860 lock = threading.Lock()
2861 diff_iter = iter(by_size) # accessed under lock
2862
2863 def worker():
2864 try:
2865 lock.acquire()
2866 for d in diff_iter:
2867 lock.release()
2868 start = time.time()
2869 d.ComputePatch()
2870 dur = time.time() - start
2871 lock.acquire()
2872
2873 tf, sf, patch = d.GetPatch()
2874 if sf.name == tf.name:
2875 name = tf.name
2876 else:
2877 name = "%s (%s)" % (tf.name, sf.name)
2878 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002879 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002880 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002881 logger.info(
2882 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2883 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002884 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002885 except Exception:
2886 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002887 raise
2888
2889 # start worker threads; wait for them all to finish.
2890 threads = [threading.Thread(target=worker)
2891 for i in range(OPTIONS.worker_threads)]
2892 for th in threads:
2893 th.start()
2894 while threads:
2895 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002896
2897
Dan Albert8b72aef2015-03-23 19:13:21 -07002898class BlockDifference(object):
2899 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002900 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002901 self.tgt = tgt
2902 self.src = src
2903 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002904 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002905 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002906
Tao Baodd2a5892015-03-12 12:32:37 -07002907 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002908 version = max(
2909 int(i) for i in
2910 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002911 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002912 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002913
Tianjie Xu41976c72019-07-03 13:57:01 -07002914 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2915 version=self.version,
2916 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002917 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002918 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002919 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002920 self.touched_src_ranges = b.touched_src_ranges
2921 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002922
Yifan Hong10c530d2018-12-27 17:34:18 -08002923 # On devices with dynamic partitions, for new partitions,
2924 # src is None but OPTIONS.source_info_dict is not.
2925 if OPTIONS.source_info_dict is None:
2926 is_dynamic_build = OPTIONS.info_dict.get(
2927 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002928 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002929 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002930 is_dynamic_build = OPTIONS.source_info_dict.get(
2931 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002932 is_dynamic_source = partition in shlex.split(
2933 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002934
Yifan Hongbb2658d2019-01-25 12:30:58 -08002935 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002936 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2937
Yifan Hongbb2658d2019-01-25 12:30:58 -08002938 # For dynamic partitions builds, check partition list in both source
2939 # and target build because new partitions may be added, and existing
2940 # partitions may be removed.
2941 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2942
Yifan Hong10c530d2018-12-27 17:34:18 -08002943 if is_dynamic:
2944 self.device = 'map_partition("%s")' % partition
2945 else:
2946 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002947 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2948 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002949 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002950 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2951 OPTIONS.source_info_dict)
2952 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002953
Tao Baod8d14be2016-02-04 14:26:02 -08002954 @property
2955 def required_cache(self):
2956 return self._required_cache
2957
Tao Bao76def242017-11-21 09:25:31 -08002958 def WriteScript(self, script, output_zip, progress=None,
2959 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002960 if not self.src:
2961 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002962 script.Print("Patching %s image unconditionally..." % (self.partition,))
2963 else:
2964 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002965
Dan Albert8b72aef2015-03-23 19:13:21 -07002966 if progress:
2967 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002968 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002969
2970 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002971 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002972
Tao Bao9bc6bb22015-11-09 16:58:28 -08002973 def WriteStrictVerifyScript(self, script):
2974 """Verify all the blocks in the care_map, including clobbered blocks.
2975
2976 This differs from the WriteVerifyScript() function: a) it prints different
2977 error messages; b) it doesn't allow half-way updated images to pass the
2978 verification."""
2979
2980 partition = self.partition
2981 script.Print("Verifying %s..." % (partition,))
2982 ranges = self.tgt.care_map
2983 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002984 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002985 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2986 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002987 self.device, ranges_str,
2988 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002989 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002990 script.AppendExtra("")
2991
Tao Baod522bdc2016-04-12 15:53:16 -07002992 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002993 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002994
2995 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002996 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002997 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002998
2999 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003000 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003001 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003002 ranges = self.touched_src_ranges
3003 expected_sha1 = self.touched_src_sha1
3004 else:
3005 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3006 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003007
3008 # No blocks to be checked, skipping.
3009 if not ranges:
3010 return
3011
Tao Bao5ece99d2015-05-12 11:42:31 -07003012 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003013 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003014 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003015 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3016 '"%s.patch.dat")) then' % (
3017 self.device, ranges_str, expected_sha1,
3018 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003019 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003020 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003021
Tianjie Xufc3422a2015-12-15 11:53:59 -08003022 if self.version >= 4:
3023
3024 # Bug: 21124327
3025 # When generating incrementals for the system and vendor partitions in
3026 # version 4 or newer, explicitly check the first block (which contains
3027 # the superblock) of the partition to see if it's what we expect. If
3028 # this check fails, give an explicit log message about the partition
3029 # having been remounted R/W (the most likely explanation).
3030 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003031 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003032
3033 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003034 if partition == "system":
3035 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3036 else:
3037 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003038 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003039 'ifelse (block_image_recover({device}, "{ranges}") && '
3040 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003041 'package_extract_file("{partition}.transfer.list"), '
3042 '"{partition}.new.dat", "{partition}.patch.dat"), '
3043 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003044 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003045 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003046 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003047
Tao Baodd2a5892015-03-12 12:32:37 -07003048 # Abort the OTA update. Note that the incremental OTA cannot be applied
3049 # even if it may match the checksum of the target partition.
3050 # a) If version < 3, operations like move and erase will make changes
3051 # unconditionally and damage the partition.
3052 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003053 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003054 if partition == "system":
3055 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3056 else:
3057 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3058 script.AppendExtra((
3059 'abort("E%d: %s partition has unexpected contents");\n'
3060 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003061
Yifan Hong10c530d2018-12-27 17:34:18 -08003062 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003063 partition = self.partition
3064 script.Print('Verifying the updated %s image...' % (partition,))
3065 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3066 ranges = self.tgt.care_map
3067 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003068 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003069 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003070 self.device, ranges_str,
3071 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003072
3073 # Bug: 20881595
3074 # Verify that extended blocks are really zeroed out.
3075 if self.tgt.extended:
3076 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003077 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003078 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003079 self.device, ranges_str,
3080 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003081 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003082 if partition == "system":
3083 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3084 else:
3085 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003086 script.AppendExtra(
3087 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003088 ' abort("E%d: %s partition has unexpected non-zero contents after '
3089 'OTA update");\n'
3090 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003091 else:
3092 script.Print('Verified the updated %s image.' % (partition,))
3093
Tianjie Xu209db462016-05-24 17:34:52 -07003094 if partition == "system":
3095 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3096 else:
3097 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3098
Tao Bao5fcaaef2015-06-01 13:40:49 -07003099 script.AppendExtra(
3100 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003101 ' abort("E%d: %s partition has unexpected contents after OTA '
3102 'update");\n'
3103 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003104
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003105 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003106 ZipWrite(output_zip,
3107 '{}.transfer.list'.format(self.path),
3108 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003109
Tao Bao76def242017-11-21 09:25:31 -08003110 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3111 # its size. Quailty 9 almost triples the compression time but doesn't
3112 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003113 # zip | brotli(quality 6) | brotli(quality 9)
3114 # compressed_size: 942M | 869M (~8% reduced) | 854M
3115 # compression_time: 75s | 265s | 719s
3116 # decompression_time: 15s | 25s | 25s
3117
3118 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003119 brotli_cmd = ['brotli', '--quality=6',
3120 '--output={}.new.dat.br'.format(self.path),
3121 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003122 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003123 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003124
3125 new_data_name = '{}.new.dat.br'.format(self.partition)
3126 ZipWrite(output_zip,
3127 '{}.new.dat.br'.format(self.path),
3128 new_data_name,
3129 compress_type=zipfile.ZIP_STORED)
3130 else:
3131 new_data_name = '{}.new.dat'.format(self.partition)
3132 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3133
Dan Albert8e0178d2015-01-27 15:53:15 -08003134 ZipWrite(output_zip,
3135 '{}.patch.dat'.format(self.path),
3136 '{}.patch.dat'.format(self.partition),
3137 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003138
Tianjie Xu209db462016-05-24 17:34:52 -07003139 if self.partition == "system":
3140 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3141 else:
3142 code = ErrorCode.VENDOR_UPDATE_FAILURE
3143
Yifan Hong10c530d2018-12-27 17:34:18 -08003144 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003145 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003146 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003147 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003148 device=self.device, partition=self.partition,
3149 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003150 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003151
Kelvin Zhang0876c412020-06-23 15:06:58 -04003152 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003153 data = source.ReadRangeSet(ranges)
3154 ctx = sha1()
3155
3156 for p in data:
3157 ctx.update(p)
3158
3159 return ctx.hexdigest()
3160
Kelvin Zhang0876c412020-06-23 15:06:58 -04003161 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003162 """Return the hash value for all zero blocks."""
3163 zero_block = '\x00' * 4096
3164 ctx = sha1()
3165 for _ in range(num_blocks):
3166 ctx.update(zero_block)
3167
3168 return ctx.hexdigest()
3169
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003170
Tianjie Xu41976c72019-07-03 13:57:01 -07003171# Expose these two classes to support vendor-specific scripts
3172DataImage = images.DataImage
3173EmptyImage = images.EmptyImage
3174
Tao Bao76def242017-11-21 09:25:31 -08003175
Doug Zongker96a57e72010-09-26 14:57:41 -07003176# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003177PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003178 "ext4": "EMMC",
3179 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003180 "f2fs": "EMMC",
3181 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003182}
Doug Zongker96a57e72010-09-26 14:57:41 -07003183
Kelvin Zhang0876c412020-06-23 15:06:58 -04003184
Yifan Hongbdb32012020-05-07 12:38:53 -07003185def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3186 """
3187 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3188 backwards compatibility. It aborts if the fstab entry has slotselect option
3189 (unless check_no_slot is explicitly set to False).
3190 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003191 fstab = info["fstab"]
3192 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003193 if check_no_slot:
3194 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003195 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003196 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3197 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003198 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003199
3200
Yifan Hongbdb32012020-05-07 12:38:53 -07003201def GetTypeAndDeviceExpr(mount_point, info):
3202 """
3203 Return the filesystem of the partition, and an edify expression that evaluates
3204 to the device at runtime.
3205 """
3206 fstab = info["fstab"]
3207 if fstab:
3208 p = fstab[mount_point]
3209 device_expr = '"%s"' % fstab[mount_point].device
3210 if p.slotselect:
3211 device_expr = 'add_slot_suffix(%s)' % device_expr
3212 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003213 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003214
3215
3216def GetEntryForDevice(fstab, device):
3217 """
3218 Returns:
3219 The first entry in fstab whose device is the given value.
3220 """
3221 if not fstab:
3222 return None
3223 for mount_point in fstab:
3224 if fstab[mount_point].device == device:
3225 return fstab[mount_point]
3226 return None
3227
Kelvin Zhang0876c412020-06-23 15:06:58 -04003228
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003229def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003230 """Parses and converts a PEM-encoded certificate into DER-encoded.
3231
3232 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3233
3234 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003235 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003236 """
3237 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003238 save = False
3239 for line in data.split("\n"):
3240 if "--END CERTIFICATE--" in line:
3241 break
3242 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003243 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003244 if "--BEGIN CERTIFICATE--" in line:
3245 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003246 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003247 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003248
Tao Bao04e1f012018-02-04 12:13:35 -08003249
3250def ExtractPublicKey(cert):
3251 """Extracts the public key (PEM-encoded) from the given certificate file.
3252
3253 Args:
3254 cert: The certificate filename.
3255
3256 Returns:
3257 The public key string.
3258
3259 Raises:
3260 AssertionError: On non-zero return from 'openssl'.
3261 """
3262 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3263 # While openssl 1.1 writes the key into the given filename followed by '-out',
3264 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3265 # stdout instead.
3266 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3267 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3268 pubkey, stderrdata = proc.communicate()
3269 assert proc.returncode == 0, \
3270 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3271 return pubkey
3272
3273
Tao Bao1ac886e2019-06-26 11:58:22 -07003274def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003275 """Extracts the AVB public key from the given public or private key.
3276
3277 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003278 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003279 key: The input key file, which should be PEM-encoded public or private key.
3280
3281 Returns:
3282 The path to the extracted AVB public key file.
3283 """
3284 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3285 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003286 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003287 return output
3288
3289
Doug Zongker412c02f2014-02-13 10:58:24 -08003290def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3291 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003292 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003293
Tao Bao6d5d6232018-03-09 17:04:42 -08003294 Most of the space in the boot and recovery images is just the kernel, which is
3295 identical for the two, so the resulting patch should be efficient. Add it to
3296 the output zip, along with a shell script that is run from init.rc on first
3297 boot to actually do the patching and install the new recovery image.
3298
3299 Args:
3300 input_dir: The top-level input directory of the target-files.zip.
3301 output_sink: The callback function that writes the result.
3302 recovery_img: File object for the recovery image.
3303 boot_img: File objects for the boot image.
3304 info_dict: A dict returned by common.LoadInfoDict() on the input
3305 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003306 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003307 if info_dict is None:
3308 info_dict = OPTIONS.info_dict
3309
Tao Bao6d5d6232018-03-09 17:04:42 -08003310 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003311 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3312
3313 if board_uses_vendorimage:
3314 # In this case, the output sink is rooted at VENDOR
3315 recovery_img_path = "etc/recovery.img"
3316 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3317 sh_dir = "bin"
3318 else:
3319 # In this case the output sink is rooted at SYSTEM
3320 recovery_img_path = "vendor/etc/recovery.img"
3321 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3322 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003323
Tao Baof2cffbd2015-07-22 12:33:18 -07003324 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003325 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003326
3327 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003328 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003329 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003330 # With system-root-image, boot and recovery images will have mismatching
3331 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3332 # to handle such a case.
3333 if system_root_image:
3334 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003335 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003336 assert not os.path.exists(path)
3337 else:
3338 diff_program = ["imgdiff"]
3339 if os.path.exists(path):
3340 diff_program.append("-b")
3341 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003342 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003343 else:
3344 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003345
3346 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3347 _, _, patch = d.ComputePatch()
3348 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003349
Dan Albertebb19aa2015-03-27 19:11:53 -07003350 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003351 # The following GetTypeAndDevice()s need to use the path in the target
3352 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003353 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3354 check_no_slot=False)
3355 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3356 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003357 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003358 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003359
Tao Baof2cffbd2015-07-22 12:33:18 -07003360 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003361
3362 # Note that we use /vendor to refer to the recovery resources. This will
3363 # work for a separate vendor partition mounted at /vendor or a
3364 # /system/vendor subdirectory on the system partition, for which init will
3365 # create a symlink from /vendor to /system/vendor.
3366
3367 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003368if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3369 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003370 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003371 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3372 log -t recovery "Installing new recovery image: succeeded" || \\
3373 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003374else
3375 log -t recovery "Recovery image already installed"
3376fi
3377""" % {'type': recovery_type,
3378 'device': recovery_device,
3379 'sha1': recovery_img.sha1,
3380 'size': recovery_img.size}
3381 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003382 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003383if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3384 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003385 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003386 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3387 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3388 log -t recovery "Installing new recovery image: succeeded" || \\
3389 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003390else
3391 log -t recovery "Recovery image already installed"
3392fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003393""" % {'boot_size': boot_img.size,
3394 'boot_sha1': boot_img.sha1,
3395 'recovery_size': recovery_img.size,
3396 'recovery_sha1': recovery_img.sha1,
3397 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003398 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003399 'recovery_type': recovery_type,
3400 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003401 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003402
Bill Peckhame868aec2019-09-17 17:06:47 -07003403 # The install script location moved from /system/etc to /system/bin in the L
3404 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3405 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003406
Tao Bao32fcdab2018-10-12 10:30:39 -07003407 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003408
Tao Baoda30cfa2017-12-01 16:19:46 -08003409 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003410
3411
3412class DynamicPartitionUpdate(object):
3413 def __init__(self, src_group=None, tgt_group=None, progress=None,
3414 block_difference=None):
3415 self.src_group = src_group
3416 self.tgt_group = tgt_group
3417 self.progress = progress
3418 self.block_difference = block_difference
3419
3420 @property
3421 def src_size(self):
3422 if not self.block_difference:
3423 return 0
3424 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3425
3426 @property
3427 def tgt_size(self):
3428 if not self.block_difference:
3429 return 0
3430 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3431
3432 @staticmethod
3433 def _GetSparseImageSize(img):
3434 if not img:
3435 return 0
3436 return img.blocksize * img.total_blocks
3437
3438
3439class DynamicGroupUpdate(object):
3440 def __init__(self, src_size=None, tgt_size=None):
3441 # None: group does not exist. 0: no size limits.
3442 self.src_size = src_size
3443 self.tgt_size = tgt_size
3444
3445
3446class DynamicPartitionsDifference(object):
3447 def __init__(self, info_dict, block_diffs, progress_dict=None,
3448 source_info_dict=None):
3449 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003450 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003451
3452 self._remove_all_before_apply = False
3453 if source_info_dict is None:
3454 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003455 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003456
Tao Baof1113e92019-06-18 12:10:14 -07003457 block_diff_dict = collections.OrderedDict(
3458 [(e.partition, e) for e in block_diffs])
3459
Yifan Hong10c530d2018-12-27 17:34:18 -08003460 assert len(block_diff_dict) == len(block_diffs), \
3461 "Duplicated BlockDifference object for {}".format(
3462 [partition for partition, count in
3463 collections.Counter(e.partition for e in block_diffs).items()
3464 if count > 1])
3465
Yifan Hong79997e52019-01-23 16:56:19 -08003466 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003467
3468 for p, block_diff in block_diff_dict.items():
3469 self._partition_updates[p] = DynamicPartitionUpdate()
3470 self._partition_updates[p].block_difference = block_diff
3471
3472 for p, progress in progress_dict.items():
3473 if p in self._partition_updates:
3474 self._partition_updates[p].progress = progress
3475
3476 tgt_groups = shlex.split(info_dict.get(
3477 "super_partition_groups", "").strip())
3478 src_groups = shlex.split(source_info_dict.get(
3479 "super_partition_groups", "").strip())
3480
3481 for g in tgt_groups:
3482 for p in shlex.split(info_dict.get(
3483 "super_%s_partition_list" % g, "").strip()):
3484 assert p in self._partition_updates, \
3485 "{} is in target super_{}_partition_list but no BlockDifference " \
3486 "object is provided.".format(p, g)
3487 self._partition_updates[p].tgt_group = g
3488
3489 for g in src_groups:
3490 for p in shlex.split(source_info_dict.get(
3491 "super_%s_partition_list" % g, "").strip()):
3492 assert p in self._partition_updates, \
3493 "{} is in source super_{}_partition_list but no BlockDifference " \
3494 "object is provided.".format(p, g)
3495 self._partition_updates[p].src_group = g
3496
Yifan Hong45433e42019-01-18 13:55:25 -08003497 target_dynamic_partitions = set(shlex.split(info_dict.get(
3498 "dynamic_partition_list", "").strip()))
3499 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3500 if u.tgt_size)
3501 assert block_diffs_with_target == target_dynamic_partitions, \
3502 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3503 list(target_dynamic_partitions), list(block_diffs_with_target))
3504
3505 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3506 "dynamic_partition_list", "").strip()))
3507 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3508 if u.src_size)
3509 assert block_diffs_with_source == source_dynamic_partitions, \
3510 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3511 list(source_dynamic_partitions), list(block_diffs_with_source))
3512
Yifan Hong10c530d2018-12-27 17:34:18 -08003513 if self._partition_updates:
3514 logger.info("Updating dynamic partitions %s",
3515 self._partition_updates.keys())
3516
Yifan Hong79997e52019-01-23 16:56:19 -08003517 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003518
3519 for g in tgt_groups:
3520 self._group_updates[g] = DynamicGroupUpdate()
3521 self._group_updates[g].tgt_size = int(info_dict.get(
3522 "super_%s_group_size" % g, "0").strip())
3523
3524 for g in src_groups:
3525 if g not in self._group_updates:
3526 self._group_updates[g] = DynamicGroupUpdate()
3527 self._group_updates[g].src_size = int(source_info_dict.get(
3528 "super_%s_group_size" % g, "0").strip())
3529
3530 self._Compute()
3531
3532 def WriteScript(self, script, output_zip, write_verify_script=False):
3533 script.Comment('--- Start patching dynamic partitions ---')
3534 for p, u in self._partition_updates.items():
3535 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3536 script.Comment('Patch partition %s' % p)
3537 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3538 write_verify_script=False)
3539
3540 op_list_path = MakeTempFile()
3541 with open(op_list_path, 'w') as f:
3542 for line in self._op_list:
3543 f.write('{}\n'.format(line))
3544
3545 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3546
3547 script.Comment('Update dynamic partition metadata')
3548 script.AppendExtra('assert(update_dynamic_partitions('
3549 'package_extract_file("dynamic_partitions_op_list")));')
3550
3551 if write_verify_script:
3552 for p, u in self._partition_updates.items():
3553 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3554 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003555 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003556
3557 for p, u in self._partition_updates.items():
3558 if u.tgt_size and u.src_size <= u.tgt_size:
3559 script.Comment('Patch partition %s' % p)
3560 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3561 write_verify_script=write_verify_script)
3562 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003563 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003564
3565 script.Comment('--- End patching dynamic partitions ---')
3566
3567 def _Compute(self):
3568 self._op_list = list()
3569
3570 def append(line):
3571 self._op_list.append(line)
3572
3573 def comment(line):
3574 self._op_list.append("# %s" % line)
3575
3576 if self._remove_all_before_apply:
3577 comment('Remove all existing dynamic partitions and groups before '
3578 'applying full OTA')
3579 append('remove_all_groups')
3580
3581 for p, u in self._partition_updates.items():
3582 if u.src_group and not u.tgt_group:
3583 append('remove %s' % p)
3584
3585 for p, u in self._partition_updates.items():
3586 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3587 comment('Move partition %s from %s to default' % (p, u.src_group))
3588 append('move %s default' % p)
3589
3590 for p, u in self._partition_updates.items():
3591 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3592 comment('Shrink partition %s from %d to %d' %
3593 (p, u.src_size, u.tgt_size))
3594 append('resize %s %s' % (p, u.tgt_size))
3595
3596 for g, u in self._group_updates.items():
3597 if u.src_size is not None and u.tgt_size is None:
3598 append('remove_group %s' % g)
3599 if (u.src_size is not None and u.tgt_size is not None and
3600 u.src_size > u.tgt_size):
3601 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3602 append('resize_group %s %d' % (g, u.tgt_size))
3603
3604 for g, u in self._group_updates.items():
3605 if u.src_size is None and u.tgt_size is not None:
3606 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3607 append('add_group %s %d' % (g, u.tgt_size))
3608 if (u.src_size is not None and u.tgt_size is not None and
3609 u.src_size < u.tgt_size):
3610 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3611 append('resize_group %s %d' % (g, u.tgt_size))
3612
3613 for p, u in self._partition_updates.items():
3614 if u.tgt_group and not u.src_group:
3615 comment('Add partition %s to group %s' % (p, u.tgt_group))
3616 append('add %s %s' % (p, u.tgt_group))
3617
3618 for p, u in self._partition_updates.items():
3619 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003620 comment('Grow partition %s from %d to %d' %
3621 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003622 append('resize %s %d' % (p, u.tgt_size))
3623
3624 for p, u in self._partition_updates.items():
3625 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3626 comment('Move partition %s from default to %s' %
3627 (p, u.tgt_group))
3628 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003629
3630
Yifan Hong85ac5012021-01-07 14:43:46 -08003631def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003632 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003633 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003634
3635 Args:
3636 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3637
3638 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003639 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003640 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003641 tmp_dir = MakeTempDir('boot_', suffix='.img')
3642 try:
3643 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3644 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3645 if not os.path.isfile(ramdisk):
3646 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3647 return None
3648 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3649 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3650
3651 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3652 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3653 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3654 # the host environment.
3655 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3656 cwd=extracted_ramdisk)
3657
Yifan Hongc65a0542021-01-07 14:21:01 -08003658 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3659 prop_file = os.path.join(extracted_ramdisk, search_path)
3660 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003661 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003662 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3663
Yifan Hong7dc51172021-01-12 11:27:39 -08003664 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003665
Yifan Hong85ac5012021-01-07 14:43:46 -08003666 except ExternalError as e:
3667 logger.warning('Unable to get boot image build props: %s', e)
3668 return None
3669
3670
3671def GetBootImageTimestamp(boot_img):
3672 """
3673 Get timestamp from ramdisk within the boot image
3674
3675 Args:
3676 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3677
3678 Return:
3679 An integer that corresponds to the timestamp of the boot image, or None
3680 if file has unknown format. Raise exception if an unexpected error has
3681 occurred.
3682 """
3683 prop_file = GetBootImageBuildProp(boot_img)
3684 if not prop_file:
3685 return None
3686
3687 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3688 if props is None:
3689 return None
3690
3691 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003692 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3693 if timestamp:
3694 return int(timestamp)
3695 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3696 return None
3697
3698 except ExternalError as e:
3699 logger.warning('Unable to get boot image timestamp: %s', e)
3700 return None