blob: a5bcabca6adf3f46c399ee1250e10123c3a1409b [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hong5057b952021-01-07 14:09:57 -0800131# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800132PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800133
Yifan Hongc65a0542021-01-07 14:21:01 -0800134# See sysprop.mk. If file is moved, add new search paths here; don't remove
135# existing search paths.
136RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700137
Tianjie Xu209db462016-05-24 17:34:52 -0700138class ErrorCode(object):
139 """Define error_codes for failures that happen during the actual
140 update package installation.
141
142 Error codes 0-999 are reserved for failures before the package
143 installation (i.e. low battery, package verification failure).
144 Detailed code in 'bootable/recovery/error_code.h' """
145
146 SYSTEM_VERIFICATION_FAILURE = 1000
147 SYSTEM_UPDATE_FAILURE = 1001
148 SYSTEM_UNEXPECTED_CONTENTS = 1002
149 SYSTEM_NONZERO_CONTENTS = 1003
150 SYSTEM_RECOVER_FAILURE = 1004
151 VENDOR_VERIFICATION_FAILURE = 2000
152 VENDOR_UPDATE_FAILURE = 2001
153 VENDOR_UNEXPECTED_CONTENTS = 2002
154 VENDOR_NONZERO_CONTENTS = 2003
155 VENDOR_RECOVER_FAILURE = 2004
156 OEM_PROP_MISMATCH = 3000
157 FINGERPRINT_MISMATCH = 3001
158 THUMBPRINT_MISMATCH = 3002
159 OLDER_BUILD = 3003
160 DEVICE_MISMATCH = 3004
161 BAD_PATCH_FILE = 3005
162 INSUFFICIENT_CACHE_SPACE = 3006
163 TUNE_PARTITION_FAILURE = 3007
164 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800165
Tao Bao80921982018-03-21 21:02:19 -0700166
Dan Albert8b72aef2015-03-23 19:13:21 -0700167class ExternalError(RuntimeError):
168 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700169
170
Tao Bao32fcdab2018-10-12 10:30:39 -0700171def InitLogging():
172 DEFAULT_LOGGING_CONFIG = {
173 'version': 1,
174 'disable_existing_loggers': False,
175 'formatters': {
176 'standard': {
177 'format':
178 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
179 'datefmt': '%Y-%m-%d %H:%M:%S',
180 },
181 },
182 'handlers': {
183 'default': {
184 'class': 'logging.StreamHandler',
185 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700186 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 },
188 },
189 'loggers': {
190 '': {
191 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700193 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 }
195 }
196 }
197 env_config = os.getenv('LOGGING_CONFIG')
198 if env_config:
199 with open(env_config) as f:
200 config = json.load(f)
201 else:
202 config = DEFAULT_LOGGING_CONFIG
203
204 # Increase the logging level for verbose mode.
205 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700206 config = copy.deepcopy(config)
207 config['handlers']['default']['level'] = 'INFO'
208
209 if OPTIONS.logfile:
210 config = copy.deepcopy(config)
211 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400212 'class': 'logging.FileHandler',
213 'formatter': 'standard',
214 'level': 'INFO',
215 'mode': 'w',
216 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700217 }
218 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700219
220 logging.config.dictConfig(config)
221
222
Yifan Hong8e332ff2020-07-29 17:51:55 -0700223def SetHostToolLocation(tool_name, location):
224 OPTIONS.host_tools[tool_name] = location
225
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900226def FindHostToolPath(tool_name):
227 """Finds the path to the host tool.
228
229 Args:
230 tool_name: name of the tool to find
231 Returns:
232 path to the tool if found under either one of the host_tools map or under
233 the same directory as this binary is located at. If not found, tool_name
234 is returned.
235 """
236 if tool_name in OPTIONS.host_tools:
237 return OPTIONS.host_tools[tool_name]
238
239 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 tool_path = os.path.join(my_dir, tool_name)
241 if os.path.exists(tool_path):
242 return tool_path
243
244 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700245
Tao Bao39451582017-05-04 11:10:47 -0700246def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700248
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 Args:
250 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700251 verbose: Whether the commands should be shown. Default to the global
252 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
254 stdin, etc. stdout and stderr will default to subprocess.PIPE and
255 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800256 universal_newlines will default to True, as most of the users in
257 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700258
259 Returns:
260 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700261 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700262 if 'stdout' not in kwargs and 'stderr' not in kwargs:
263 kwargs['stdout'] = subprocess.PIPE
264 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800265 if 'universal_newlines' not in kwargs:
266 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700267
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900268 if args:
269 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900271 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272
Tao Bao32fcdab2018-10-12 10:30:39 -0700273 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400274 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700276 return subprocess.Popen(args, **kwargs)
277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800403 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800411 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800459
460 # Boot image uses ro.[product.]bootimage instead of boot.
461 prop_partition = "bootimage" if partition == "boot" else partition
462
Daniel Normand5fe8622020-01-08 17:01:11 -0800463 # If provided a partition for this property, only look within that
464 # partition's build.prop.
465 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800466 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800467 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800468 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000469
470 prop_val = self._GetRawBuildProp(prop, partition)
471 if prop_val is not None:
472 return prop_val
473 raise ExternalError("couldn't find %s in %s.build.prop" %
474 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800475
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800477 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
479 return self._ResolveRoProductBuildProp(prop)
480
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 prop_val = self._GetRawBuildProp(prop, None)
482 if prop_val is not None:
483 return prop_val
484
485 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700486
487 def _ResolveRoProductBuildProp(self, prop):
488 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700490 if prop_val:
491 return prop_val
492
Steven Laver8e2086e2020-04-27 16:26:31 -0700493 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 source_order_val = self._GetRawBuildProp(
495 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700496 if source_order_val:
497 source_order = source_order_val.split(",")
498 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700499 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700500
501 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700503 raise ExternalError(
504 "Invalid ro.product.property_source_order '{}'".format(source_order))
505
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000506 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700507 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 "ro.product", "ro.product.{}".format(source_partition), 1)
509 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700510 if prop_val:
511 return prop_val
512
513 raise ExternalError("couldn't resolve {}".format(prop))
514
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 def _GetRoProductPropsDefaultSourceOrder(self):
516 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
517 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000518 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700519 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if android_version == "10":
522 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
523 # NOTE: float() conversion of android_version will have rounding error.
524 # We are checking for "9" or less, and using "< 10" is well outside of
525 # possible floating point rounding.
526 try:
527 android_version_val = float(android_version)
528 except ValueError:
529 android_version_val = 0
530 if android_version_val < 10:
531 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
532 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
533
Tianjieb37c5be2020-10-15 21:27:10 -0700534 def _GetPlatformVersion(self):
535 version_sdk = self.GetBuildProp("ro.build.version.sdk")
536 # init code switches to version_release_or_codename (see b/158483506). After
537 # API finalization, release_or_codename will be the same as release. This
538 # is the best effort to support pre-S dev stage builds.
539 if int(version_sdk) >= 30:
540 try:
541 return self.GetBuildProp("ro.build.version.release_or_codename")
542 except ExternalError:
543 logger.warning('Failed to find ro.build.version.release_or_codename')
544
545 return self.GetBuildProp("ro.build.version.release")
546
547 def _GetPartitionPlatformVersion(self, partition):
548 try:
549 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
550 partition)
551 except ExternalError:
552 return self.GetPartitionBuildProp("ro.build.version.release",
553 partition)
554
Tao Bao1c320f82019-10-04 23:25:12 -0700555 def GetOemProperty(self, key):
556 if self.oem_props is not None and key in self.oem_props:
557 return self.oem_dicts[0][key]
558 return self.GetBuildProp(key)
559
Daniel Normand5fe8622020-01-08 17:01:11 -0800560 def GetPartitionFingerprint(self, partition):
561 return self._partition_fingerprints.get(partition, None)
562
563 def CalculatePartitionFingerprint(self, partition):
564 try:
565 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
566 except ExternalError:
567 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
568 self.GetPartitionBuildProp("ro.product.brand", partition),
569 self.GetPartitionBuildProp("ro.product.name", partition),
570 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700571 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800572 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400573 self.GetPartitionBuildProp(
574 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800575 self.GetPartitionBuildProp("ro.build.type", partition),
576 self.GetPartitionBuildProp("ro.build.tags", partition))
577
Tao Bao1c320f82019-10-04 23:25:12 -0700578 def CalculateFingerprint(self):
579 if self.oem_props is None:
580 try:
581 return self.GetBuildProp("ro.build.fingerprint")
582 except ExternalError:
583 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
584 self.GetBuildProp("ro.product.brand"),
585 self.GetBuildProp("ro.product.name"),
586 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700587 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700588 self.GetBuildProp("ro.build.id"),
589 self.GetBuildProp("ro.build.version.incremental"),
590 self.GetBuildProp("ro.build.type"),
591 self.GetBuildProp("ro.build.tags"))
592 return "%s/%s/%s:%s" % (
593 self.GetOemProperty("ro.product.brand"),
594 self.GetOemProperty("ro.product.name"),
595 self.GetOemProperty("ro.product.device"),
596 self.GetBuildProp("ro.build.thumbprint"))
597
598 def WriteMountOemScript(self, script):
599 assert self.oem_props is not None
600 recovery_mount_options = self.info_dict.get("recovery_mount_options")
601 script.Mount("/oem", recovery_mount_options)
602
603 def WriteDeviceAssertions(self, script, oem_no_mount):
604 # Read the property directly if not using OEM properties.
605 if not self.oem_props:
606 script.AssertDevice(self.device)
607 return
608
609 # Otherwise assert OEM properties.
610 if not self.oem_dicts:
611 raise ExternalError(
612 "No OEM file provided to answer expected assertions")
613
614 for prop in self.oem_props.split():
615 values = []
616 for oem_dict in self.oem_dicts:
617 if prop in oem_dict:
618 values.append(oem_dict[prop])
619 if not values:
620 raise ExternalError(
621 "The OEM file is missing the property %s" % (prop,))
622 script.AssertOemProperty(prop, values, oem_no_mount)
623
624
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000625def ReadFromInputFile(input_file, fn):
626 """Reads the contents of fn from input zipfile or directory."""
627 if isinstance(input_file, zipfile.ZipFile):
628 return input_file.read(fn).decode()
629 else:
630 path = os.path.join(input_file, *fn.split("/"))
631 try:
632 with open(path) as f:
633 return f.read()
634 except IOError as e:
635 if e.errno == errno.ENOENT:
636 raise KeyError(fn)
637
638
Yifan Hong10482a22021-01-07 14:38:41 -0800639def ExtractFromInputFile(input_file, fn):
640 """Extracts the contents of fn from input zipfile or directory into a file."""
641 if isinstance(input_file, zipfile.ZipFile):
642 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500643 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800644 f.write(input_file.read(fn))
645 return tmp_file
646 else:
647 file = os.path.join(input_file, *fn.split("/"))
648 if not os.path.exists(file):
649 raise KeyError(fn)
650 return file
651
652
Tao Bao410ad8b2018-08-24 12:08:38 -0700653def LoadInfoDict(input_file, repacking=False):
654 """Loads the key/value pairs from the given input target_files.
655
Tianjiea85bdf02020-07-29 11:56:19 -0700656 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700657 checks and returns the parsed key/value pairs for to the given build. It's
658 usually called early when working on input target_files files, e.g. when
659 generating OTAs, or signing builds. Note that the function may be called
660 against an old target_files file (i.e. from past dessert releases). So the
661 property parsing needs to be backward compatible.
662
663 In a `META/misc_info.txt`, a few properties are stored as links to the files
664 in the PRODUCT_OUT directory. It works fine with the build system. However,
665 they are no longer available when (re)generating images from target_files zip.
666 When `repacking` is True, redirect these properties to the actual files in the
667 unzipped directory.
668
669 Args:
670 input_file: The input target_files file, which could be an open
671 zipfile.ZipFile instance, or a str for the dir that contains the files
672 unzipped from a target_files file.
673 repacking: Whether it's trying repack an target_files file after loading the
674 info dict (default: False). If so, it will rewrite a few loaded
675 properties (e.g. selinux_fc, root_dir) to point to the actual files in
676 target_files file. When doing repacking, `input_file` must be a dir.
677
678 Returns:
679 A dict that contains the parsed key/value pairs.
680
681 Raises:
682 AssertionError: On invalid input arguments.
683 ValueError: On malformed input values.
684 """
685 if repacking:
686 assert isinstance(input_file, str), \
687 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700688
Doug Zongkerc9253822014-02-04 12:17:58 -0800689 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000690 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800691
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700692 try:
Michael Runge6e836112014-04-15 17:40:21 -0700693 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700694 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700695 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700696
Tao Bao410ad8b2018-08-24 12:08:38 -0700697 if "recovery_api_version" not in d:
698 raise ValueError("Failed to find 'recovery_api_version'")
699 if "fstab_version" not in d:
700 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800701
Tao Bao410ad8b2018-08-24 12:08:38 -0700702 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700703 # "selinux_fc" properties should point to the file_contexts files
704 # (file_contexts.bin) under META/.
705 for key in d:
706 if key.endswith("selinux_fc"):
707 fc_basename = os.path.basename(d[key])
708 fc_config = os.path.join(input_file, "META", fc_basename)
709 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700710
Daniel Norman72c626f2019-05-13 15:58:14 -0700711 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700712
Tom Cherryd14b8952018-08-09 14:26:00 -0700713 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700714 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700715 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700716 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700717
David Anderson0ec64ac2019-12-06 12:21:18 -0800718 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700719 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700720 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800721 key_name = part_name + "_base_fs_file"
722 if key_name not in d:
723 continue
724 basename = os.path.basename(d[key_name])
725 base_fs_file = os.path.join(input_file, "META", basename)
726 if os.path.exists(base_fs_file):
727 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700728 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700729 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800730 "Failed to find %s base fs file: %s", part_name, base_fs_file)
731 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700732
Doug Zongker37974732010-09-16 17:44:38 -0700733 def makeint(key):
734 if key in d:
735 d[key] = int(d[key], 0)
736
737 makeint("recovery_api_version")
738 makeint("blocksize")
739 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700740 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700741 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700742 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700743 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800744 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700745
Steve Muckle903a1ca2020-05-07 17:32:10 -0700746 boot_images = "boot.img"
747 if "boot_images" in d:
748 boot_images = d["boot_images"]
749 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400750 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700751
Tao Bao765668f2019-10-04 22:03:00 -0700752 # Load recovery fstab if applicable.
753 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800754
Tianjie Xu861f4132018-09-12 11:49:33 -0700755 # Tries to load the build props for all partitions with care_map, including
756 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800757 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800758 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000759 d[partition_prop] = PartitionBuildProps.FromInputFile(
760 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700761 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800762
Tao Bao3ed35d32019-10-07 20:48:48 -0700763 # Set up the salt (based on fingerprint) that will be used when adding AVB
764 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800765 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700766 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800767 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800768 fingerprint = build_info.GetPartitionFingerprint(partition)
769 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400770 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400771 try:
772 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
773 except KeyError:
774 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700775 return d
776
Tao Baod1de6f32017-03-01 16:38:48 -0800777
Kelvin Zhang39aea442020-08-17 11:04:25 -0400778
Daniel Norman4cc9df62019-07-18 10:11:07 -0700779def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900780 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700781 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900782
Daniel Norman4cc9df62019-07-18 10:11:07 -0700783
784def LoadDictionaryFromFile(file_path):
785 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900786 return LoadDictionaryFromLines(lines)
787
788
Michael Runge6e836112014-04-15 17:40:21 -0700789def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700790 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700791 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700792 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700793 if not line or line.startswith("#"):
794 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700795 if "=" in line:
796 name, value = line.split("=", 1)
797 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700798 return d
799
Tao Baod1de6f32017-03-01 16:38:48 -0800800
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000801class PartitionBuildProps(object):
802 """The class holds the build prop of a particular partition.
803
804 This class loads the build.prop and holds the build properties for a given
805 partition. It also partially recognizes the 'import' statement in the
806 build.prop; and calculates alternative values of some specific build
807 properties during runtime.
808
809 Attributes:
810 input_file: a zipped target-file or an unzipped target-file directory.
811 partition: name of the partition.
812 props_allow_override: a list of build properties to search for the
813 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000814 build_props: a dict of build properties for the given partition.
815 prop_overrides: a set of props that are overridden by import.
816 placeholder_values: A dict of runtime variables' values to replace the
817 placeholders in the build.prop file. We expect exactly one value for
818 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000819 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400820
Tianjie Xu9afb2212020-05-10 21:48:15 +0000821 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000822 self.input_file = input_file
823 self.partition = name
824 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000825 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000826 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 self.prop_overrides = set()
828 self.placeholder_values = {}
829 if placeholder_values:
830 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000831
832 @staticmethod
833 def FromDictionary(name, build_props):
834 """Constructs an instance from a build prop dictionary."""
835
836 props = PartitionBuildProps("unknown", name)
837 props.build_props = build_props.copy()
838 return props
839
840 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800843
844 if name == "boot":
845 data = PartitionBuildProps._ReadBootPropFile(input_file)
846 else:
847 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
848
849 props = PartitionBuildProps(input_file, name, placeholder_values)
850 props._LoadBuildProp(data)
851 return props
852
853 @staticmethod
854 def _ReadBootPropFile(input_file):
855 """
856 Read build.prop for boot image from input_file.
857 Return empty string if not found.
858 """
859 try:
860 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
861 except KeyError:
862 logger.warning('Failed to read IMAGES/boot.img')
863 return ''
864 prop_file = GetBootImageBuildProp(boot_img)
865 if prop_file is None:
866 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500867 with open(prop_file, "r") as f:
868 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800869
870 @staticmethod
871 def _ReadPartitionPropFile(input_file, name):
872 """
873 Read build.prop for name from input_file.
874 Return empty string if not found.
875 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000876 data = ''
877 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
878 '{}/build.prop'.format(name.upper())]:
879 try:
880 data = ReadFromInputFile(input_file, prop_file)
881 break
882 except KeyError:
883 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800884 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000885
Yifan Hong125d0b62020-09-24 17:07:03 -0700886 @staticmethod
887 def FromBuildPropFile(name, build_prop_file):
888 """Constructs an instance from a build prop file."""
889
890 props = PartitionBuildProps("unknown", name)
891 with open(build_prop_file) as f:
892 props._LoadBuildProp(f.read())
893 return props
894
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 def _LoadBuildProp(self, data):
896 for line in data.split('\n'):
897 line = line.strip()
898 if not line or line.startswith("#"):
899 continue
900 if line.startswith("import"):
901 overrides = self._ImportParser(line)
902 duplicates = self.prop_overrides.intersection(overrides.keys())
903 if duplicates:
904 raise ValueError('prop {} is overridden multiple times'.format(
905 ','.join(duplicates)))
906 self.prop_overrides = self.prop_overrides.union(overrides.keys())
907 self.build_props.update(overrides)
908 elif "=" in line:
909 name, value = line.split("=", 1)
910 if name in self.prop_overrides:
911 raise ValueError('prop {} is set again after overridden by import '
912 'statement'.format(name))
913 self.build_props[name] = value
914
915 def _ImportParser(self, line):
916 """Parses the build prop in a given import statement."""
917
918 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400919 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000920 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700921
922 if len(tokens) == 3:
923 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
924 return {}
925
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 import_path = tokens[1]
927 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
928 raise ValueError('Unrecognized import path {}'.format(line))
929
930 # We only recognize a subset of import statement that the init process
931 # supports. And we can loose the restriction based on how the dynamic
932 # fingerprint is used in practice. The placeholder format should be
933 # ${placeholder}, and its value should be provided by the caller through
934 # the placeholder_values.
935 for prop, value in self.placeholder_values.items():
936 prop_place_holder = '${{{}}}'.format(prop)
937 if prop_place_holder in import_path:
938 import_path = import_path.replace(prop_place_holder, value)
939 if '$' in import_path:
940 logger.info('Unresolved place holder in import path %s', import_path)
941 return {}
942
943 import_path = import_path.replace('/{}'.format(self.partition),
944 self.partition.upper())
945 logger.info('Parsing build props override from %s', import_path)
946
947 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
948 d = LoadDictionaryFromLines(lines)
949 return {key: val for key, val in d.items()
950 if key in self.props_allow_override}
951
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000952 def GetProp(self, prop):
953 return self.build_props.get(prop)
954
955
Tianjie Xucfa86222016-03-07 16:31:19 -0800956def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
957 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700958 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700959 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700960 self.mount_point = mount_point
961 self.fs_type = fs_type
962 self.device = device
963 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700964 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700965 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700966
967 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800968 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700969 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700970 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700971 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700972
Tao Baod1de6f32017-03-01 16:38:48 -0800973 assert fstab_version == 2
974
975 d = {}
976 for line in data.split("\n"):
977 line = line.strip()
978 if not line or line.startswith("#"):
979 continue
980
981 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
982 pieces = line.split()
983 if len(pieces) != 5:
984 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
985
986 # Ignore entries that are managed by vold.
987 options = pieces[4]
988 if "voldmanaged=" in options:
989 continue
990
991 # It's a good line, parse it.
992 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700993 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800994 options = options.split(",")
995 for i in options:
996 if i.startswith("length="):
997 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700998 elif i == "slotselect":
999 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001000 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001001 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001003
Tao Baod1de6f32017-03-01 16:38:48 -08001004 mount_flags = pieces[3]
1005 # Honor the SELinux context if present.
1006 context = None
1007 for i in mount_flags.split(","):
1008 if i.startswith("context="):
1009 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001010
Tao Baod1de6f32017-03-01 16:38:48 -08001011 mount_point = pieces[1]
1012 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001013 device=pieces[0], length=length, context=context,
1014 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001015
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001016 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001017 # system. Other areas assume system is always at "/system" so point /system
1018 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001019 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001020 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001021 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001022 return d
1023
1024
Tao Bao765668f2019-10-04 22:03:00 -07001025def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1026 """Finds the path to recovery fstab and loads its contents."""
1027 # recovery fstab is only meaningful when installing an update via recovery
1028 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001029 if info_dict.get('ab_update') == 'true' and \
1030 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001031 return None
1032
1033 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1034 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1035 # cases, since it may load the info_dict from an old build (e.g. when
1036 # generating incremental OTAs from that build).
1037 system_root_image = info_dict.get('system_root_image') == 'true'
1038 if info_dict.get('no_recovery') != 'true':
1039 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1040 if isinstance(input_file, zipfile.ZipFile):
1041 if recovery_fstab_path not in input_file.namelist():
1042 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1043 else:
1044 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1045 if not os.path.exists(path):
1046 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1047 return LoadRecoveryFSTab(
1048 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1049 system_root_image)
1050
1051 if info_dict.get('recovery_as_boot') == 'true':
1052 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1053 if isinstance(input_file, zipfile.ZipFile):
1054 if recovery_fstab_path not in input_file.namelist():
1055 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1056 else:
1057 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1058 if not os.path.exists(path):
1059 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1060 return LoadRecoveryFSTab(
1061 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1062 system_root_image)
1063
1064 return None
1065
1066
Doug Zongker37974732010-09-16 17:44:38 -07001067def DumpInfoDict(d):
1068 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001069 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001070
Dan Albert8b72aef2015-03-23 19:13:21 -07001071
Daniel Norman55417142019-11-25 16:04:36 -08001072def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001073 """Merges dynamic partition info variables.
1074
1075 Args:
1076 framework_dict: The dictionary of dynamic partition info variables from the
1077 partial framework target files.
1078 vendor_dict: The dictionary of dynamic partition info variables from the
1079 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001080
1081 Returns:
1082 The merged dynamic partition info dictionary.
1083 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001084
1085 def uniq_concat(a, b):
1086 combined = set(a.split(" "))
1087 combined.update(set(b.split(" ")))
1088 combined = [item.strip() for item in combined if item.strip()]
1089 return " ".join(sorted(combined))
1090
1091 if (framework_dict.get("use_dynamic_partitions") !=
1092 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1093 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1094
1095 merged_dict = {"use_dynamic_partitions": "true"}
1096
1097 merged_dict["dynamic_partition_list"] = uniq_concat(
1098 framework_dict.get("dynamic_partition_list", ""),
1099 vendor_dict.get("dynamic_partition_list", ""))
1100
1101 # Super block devices are defined by the vendor dict.
1102 if "super_block_devices" in vendor_dict:
1103 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1104 for block_device in merged_dict["super_block_devices"].split(" "):
1105 key = "super_%s_device_size" % block_device
1106 if key not in vendor_dict:
1107 raise ValueError("Vendor dict does not contain required key %s." % key)
1108 merged_dict[key] = vendor_dict[key]
1109
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001110 # Partition groups and group sizes are defined by the vendor dict because
1111 # these values may vary for each board that uses a shared system image.
1112 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001113 for partition_group in merged_dict["super_partition_groups"].split(" "):
1114 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001115 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001116 if key not in vendor_dict:
1117 raise ValueError("Vendor dict does not contain required key %s." % key)
1118 merged_dict[key] = vendor_dict[key]
1119
1120 # Set the partition group's partition list using a concatenation of the
1121 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001122 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001123 merged_dict[key] = uniq_concat(
1124 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301125
Daniel Normanb0c75912020-09-24 14:30:21 -07001126 # Various other flags should be copied from the vendor dict, if defined.
1127 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1128 "super_metadata_device", "super_partition_error_limit",
1129 "super_partition_size"):
1130 if key in vendor_dict.keys():
1131 merged_dict[key] = vendor_dict[key]
1132
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001133 return merged_dict
1134
1135
Daniel Norman21c34f72020-11-11 17:25:50 -08001136def PartitionMapFromTargetFiles(target_files_dir):
1137 """Builds a map from partition -> path within an extracted target files directory."""
1138 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1139 possible_subdirs = {
1140 "system": ["SYSTEM"],
1141 "vendor": ["VENDOR", "SYSTEM/vendor"],
1142 "product": ["PRODUCT", "SYSTEM/product"],
1143 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1144 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1145 "vendor_dlkm": [
1146 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1147 ],
1148 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1149 }
1150 partition_map = {}
1151 for partition, subdirs in possible_subdirs.items():
1152 for subdir in subdirs:
1153 if os.path.exists(os.path.join(target_files_dir, subdir)):
1154 partition_map[partition] = subdir
1155 break
1156 return partition_map
1157
1158
Daniel Normand3351562020-10-29 12:33:11 -07001159def SharedUidPartitionViolations(uid_dict, partition_groups):
1160 """Checks for APK sharedUserIds that cross partition group boundaries.
1161
1162 This uses a single or merged build's shareduid_violation_modules.json
1163 output file, as generated by find_shareduid_violation.py or
1164 core/tasks/find-shareduid-violation.mk.
1165
1166 An error is defined as a sharedUserId that is found in a set of partitions
1167 that span more than one partition group.
1168
1169 Args:
1170 uid_dict: A dictionary created by using the standard json module to read a
1171 complete shareduid_violation_modules.json file.
1172 partition_groups: A list of groups, where each group is a list of
1173 partitions.
1174
1175 Returns:
1176 A list of error messages.
1177 """
1178 errors = []
1179 for uid, partitions in uid_dict.items():
1180 found_in_groups = [
1181 group for group in partition_groups
1182 if set(partitions.keys()) & set(group)
1183 ]
1184 if len(found_in_groups) > 1:
1185 errors.append(
1186 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1187 % (uid, ",".join(sorted(partitions.keys()))))
1188 return errors
1189
1190
Daniel Norman21c34f72020-11-11 17:25:50 -08001191def RunHostInitVerifier(product_out, partition_map):
1192 """Runs host_init_verifier on the init rc files within partitions.
1193
1194 host_init_verifier searches the etc/init path within each partition.
1195
1196 Args:
1197 product_out: PRODUCT_OUT directory, containing partition directories.
1198 partition_map: A map of partition name -> relative path within product_out.
1199 """
1200 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1201 cmd = ["host_init_verifier"]
1202 for partition, path in partition_map.items():
1203 if partition not in allowed_partitions:
1204 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1205 partition)
1206 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1207 # Add --property-contexts if the file exists on the partition.
1208 property_contexts = "%s_property_contexts" % (
1209 "plat" if partition == "system" else partition)
1210 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1211 property_contexts)
1212 if os.path.exists(property_contexts_path):
1213 cmd.append("--property-contexts=%s" % property_contexts_path)
1214 # Add the passwd file if the file exists on the partition.
1215 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1216 if os.path.exists(passwd_path):
1217 cmd.extend(["-p", passwd_path])
1218 return RunAndCheckOutput(cmd)
1219
1220
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001221def AppendAVBSigningArgs(cmd, partition):
1222 """Append signing arguments for avbtool."""
1223 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1224 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001225 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1226 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1227 if os.path.exists(new_key_path):
1228 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001229 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1230 if key_path and algorithm:
1231 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001232 avb_salt = OPTIONS.info_dict.get("avb_salt")
1233 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001234 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001235 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001236
1237
Tao Bao765668f2019-10-04 22:03:00 -07001238def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001239 """Returns the VBMeta arguments for partition.
1240
1241 It sets up the VBMeta argument by including the partition descriptor from the
1242 given 'image', or by configuring the partition as a chained partition.
1243
1244 Args:
1245 partition: The name of the partition (e.g. "system").
1246 image: The path to the partition image.
1247 info_dict: A dict returned by common.LoadInfoDict(). Will use
1248 OPTIONS.info_dict if None has been given.
1249
1250 Returns:
1251 A list of VBMeta arguments.
1252 """
1253 if info_dict is None:
1254 info_dict = OPTIONS.info_dict
1255
1256 # Check if chain partition is used.
1257 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001258 if not key_path:
1259 return ["--include_descriptors_from_image", image]
1260
1261 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1262 # into vbmeta.img. The recovery image will be configured on an independent
1263 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1264 # See details at
1265 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001266 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001267 return []
1268
1269 # Otherwise chain the partition into vbmeta.
1270 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1271 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001272
1273
Tao Bao02a08592018-07-22 12:40:45 -07001274def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1275 """Constructs and returns the arg to build or verify a chained partition.
1276
1277 Args:
1278 partition: The partition name.
1279 info_dict: The info dict to look up the key info and rollback index
1280 location.
1281 key: The key to be used for building or verifying the partition. Defaults to
1282 the key listed in info_dict.
1283
1284 Returns:
1285 A string of form "partition:rollback_index_location:key" that can be used to
1286 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001287 """
1288 if key is None:
1289 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001290 if key and not os.path.exists(key) and OPTIONS.search_path:
1291 new_key_path = os.path.join(OPTIONS.search_path, key)
1292 if os.path.exists(new_key_path):
1293 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001294 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001295 rollback_index_location = info_dict[
1296 "avb_" + partition + "_rollback_index_location"]
1297 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1298
1299
Tianjie20dd8f22020-04-19 15:51:16 -07001300def ConstructAftlMakeImageCommands(output_image):
1301 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001302
1303 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001304 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001305 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1306 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1307 'No AFTL manufacturer key provided.'
1308
1309 vbmeta_image = MakeTempFile()
1310 os.rename(output_image, vbmeta_image)
1311 build_info = BuildInfo(OPTIONS.info_dict)
1312 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001313 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001314 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001315 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001316 "--vbmeta_image_path", vbmeta_image,
1317 "--output", output_image,
1318 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001319 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001320 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1321 "--algorithm", "SHA256_RSA4096",
1322 "--padding", "4096"]
1323 if OPTIONS.aftl_signer_helper:
1324 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001325 return aftl_cmd
1326
1327
1328def AddAftlInclusionProof(output_image):
1329 """Appends the aftl inclusion proof to the vbmeta image."""
1330
1331 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 RunAndCheckOutput(aftl_cmd)
1333
1334 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1335 output_image, '--transparency_log_pub_keys',
1336 OPTIONS.aftl_key_path]
1337 RunAndCheckOutput(verify_cmd)
1338
1339
Daniel Norman276f0622019-07-26 14:13:51 -07001340def BuildVBMeta(image_path, partitions, name, needed_partitions):
1341 """Creates a VBMeta image.
1342
1343 It generates the requested VBMeta image. The requested image could be for
1344 top-level or chained VBMeta image, which is determined based on the name.
1345
1346 Args:
1347 image_path: The output path for the new VBMeta image.
1348 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001349 values. Only valid partition names are accepted, as partitions listed
1350 in common.AVB_PARTITIONS and custom partitions listed in
1351 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001352 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1353 needed_partitions: Partitions whose descriptors should be included into the
1354 generated VBMeta image.
1355
1356 Raises:
1357 AssertionError: On invalid input args.
1358 """
1359 avbtool = OPTIONS.info_dict["avb_avbtool"]
1360 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1361 AppendAVBSigningArgs(cmd, name)
1362
Hongguang Chenf23364d2020-04-27 18:36:36 -07001363 custom_partitions = OPTIONS.info_dict.get(
1364 "avb_custom_images_partition_list", "").strip().split()
1365
Daniel Norman276f0622019-07-26 14:13:51 -07001366 for partition, path in partitions.items():
1367 if partition not in needed_partitions:
1368 continue
1369 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001370 partition in AVB_VBMETA_PARTITIONS or
1371 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001372 'Unknown partition: {}'.format(partition)
1373 assert os.path.exists(path), \
1374 'Failed to find {} for {}'.format(path, partition)
1375 cmd.extend(GetAvbPartitionArg(partition, path))
1376
1377 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1378 if args and args.strip():
1379 split_args = shlex.split(args)
1380 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001381 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001382 # as a path relative to source tree, which may not be available at the
1383 # same location when running this script (we have the input target_files
1384 # zip only). For such cases, we additionally scan other locations (e.g.
1385 # IMAGES/, RADIO/, etc) before bailing out.
1386 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001387 chained_image = split_args[index + 1]
1388 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001389 continue
1390 found = False
1391 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1392 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001393 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001394 if os.path.exists(alt_path):
1395 split_args[index + 1] = alt_path
1396 found = True
1397 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001398 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001399 cmd.extend(split_args)
1400
1401 RunAndCheckOutput(cmd)
1402
Tianjie Xueaed60c2020-03-12 00:33:28 -07001403 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001404 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001405 AddAftlInclusionProof(image_path)
1406
Daniel Norman276f0622019-07-26 14:13:51 -07001407
J. Avila98cd4cc2020-06-10 20:09:10 +00001408def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001409 ramdisk_img = tempfile.NamedTemporaryFile()
1410
1411 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1412 cmd = ["mkbootfs", "-f", fs_config_file,
1413 os.path.join(sourcedir, "RAMDISK")]
1414 else:
1415 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1416 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001417 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001418 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001419 stdout=ramdisk_img.file.fileno())
1420 else:
1421 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001422
1423 p2.wait()
1424 p1.wait()
1425 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001426 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001427
1428 return ramdisk_img
1429
1430
Steve Muckle9793cf62020-04-08 18:27:00 -07001431def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001432 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001433 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001434
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001435 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001436 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1437 we are building a two-step special image (i.e. building a recovery image to
1438 be loaded into /boot in two-step OTAs).
1439
1440 Return the image data, or None if sourcedir does not appear to contains files
1441 for building the requested image.
1442 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001443
Yifan Hong63c5ca12020-10-08 11:54:02 -07001444 if info_dict is None:
1445 info_dict = OPTIONS.info_dict
1446
Steve Muckle9793cf62020-04-08 18:27:00 -07001447 # "boot" or "recovery", without extension.
1448 partition_name = os.path.basename(sourcedir).lower()
1449
Yifan Hong63c5ca12020-10-08 11:54:02 -07001450 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001451 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001452 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1453 logger.info("Excluded kernel binary from recovery image.")
1454 else:
1455 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001456 else:
1457 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001458 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001459 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001460 return None
1461
1462 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001463 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001464
Doug Zongkereef39442009-04-02 12:14:19 -07001465 img = tempfile.NamedTemporaryFile()
1466
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001467 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001468 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1469 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001470
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001471 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1472 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1473
Yifan Hong63c5ca12020-10-08 11:54:02 -07001474 cmd = [mkbootimg]
1475 if kernel:
1476 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001477
Benoit Fradina45a8682014-07-14 21:00:43 +02001478 fn = os.path.join(sourcedir, "second")
1479 if os.access(fn, os.F_OK):
1480 cmd.append("--second")
1481 cmd.append(fn)
1482
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001483 fn = os.path.join(sourcedir, "dtb")
1484 if os.access(fn, os.F_OK):
1485 cmd.append("--dtb")
1486 cmd.append(fn)
1487
Doug Zongker171f1cd2009-06-15 22:36:37 -07001488 fn = os.path.join(sourcedir, "cmdline")
1489 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001490 cmd.append("--cmdline")
1491 cmd.append(open(fn).read().rstrip("\n"))
1492
1493 fn = os.path.join(sourcedir, "base")
1494 if os.access(fn, os.F_OK):
1495 cmd.append("--base")
1496 cmd.append(open(fn).read().rstrip("\n"))
1497
Ying Wang4de6b5b2010-08-25 14:29:34 -07001498 fn = os.path.join(sourcedir, "pagesize")
1499 if os.access(fn, os.F_OK):
1500 cmd.append("--pagesize")
1501 cmd.append(open(fn).read().rstrip("\n"))
1502
Steve Mucklef84668e2020-03-16 19:13:46 -07001503 if partition_name == "recovery":
1504 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301505 if not args:
1506 # Fall back to "mkbootimg_args" for recovery image
1507 # in case "recovery_mkbootimg_args" is not set.
1508 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001509 else:
1510 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001511 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001512 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001513
Tao Bao76def242017-11-21 09:25:31 -08001514 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001515 if args and args.strip():
1516 cmd.extend(shlex.split(args))
1517
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001518 if has_ramdisk:
1519 cmd.extend(["--ramdisk", ramdisk_img.name])
1520
Tao Baod95e9fd2015-03-29 23:07:41 -07001521 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001522 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001523 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001524 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001526 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001527
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001528 if partition_name == "recovery":
1529 if info_dict.get("include_recovery_dtbo") == "true":
1530 fn = os.path.join(sourcedir, "recovery_dtbo")
1531 cmd.extend(["--recovery_dtbo", fn])
1532 if info_dict.get("include_recovery_acpio") == "true":
1533 fn = os.path.join(sourcedir, "recovery_acpio")
1534 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001535
Tao Bao986ee862018-10-04 15:46:16 -07001536 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001537
Tao Bao76def242017-11-21 09:25:31 -08001538 if (info_dict.get("boot_signer") == "true" and
1539 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001540 # Hard-code the path as "/boot" for two-step special recovery image (which
1541 # will be loaded into /boot during the two-step OTA).
1542 if two_step_image:
1543 path = "/boot"
1544 else:
Tao Baobf70c312017-07-11 17:27:55 -07001545 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001546 cmd = [OPTIONS.boot_signer_path]
1547 cmd.extend(OPTIONS.boot_signer_args)
1548 cmd.extend([path, img.name,
1549 info_dict["verity_key"] + ".pk8",
1550 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001551 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001552
Tao Baod95e9fd2015-03-29 23:07:41 -07001553 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001554 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001555 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001556 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001557 # We have switched from the prebuilt futility binary to using the tool
1558 # (futility-host) built from the source. Override the setting in the old
1559 # TF.zip.
1560 futility = info_dict["futility"]
1561 if futility.startswith("prebuilts/"):
1562 futility = "futility-host"
1563 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001564 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001565 info_dict["vboot_key"] + ".vbprivk",
1566 info_dict["vboot_subkey"] + ".vbprivk",
1567 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001568 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001569 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001570
Tao Baof3282b42015-04-01 11:21:55 -07001571 # Clean up the temp files.
1572 img_unsigned.close()
1573 img_keyblock.close()
1574
David Zeuthen8fecb282017-12-01 16:24:01 -05001575 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001576 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001577 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001578 if partition_name == "recovery":
1579 part_size = info_dict["recovery_size"]
1580 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001581 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001582 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001583 "--partition_size", str(part_size), "--partition_name",
1584 partition_name]
1585 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001586 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001587 if args and args.strip():
1588 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001589 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001590
1591 img.seek(os.SEEK_SET, 0)
1592 data = img.read()
1593
1594 if has_ramdisk:
1595 ramdisk_img.close()
1596 img.close()
1597
1598 return data
1599
1600
Doug Zongkerd5131602012-08-02 14:46:42 -07001601def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001602 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001603 """Return a File object with the desired bootable image.
1604
1605 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1606 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1607 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001608
Doug Zongker55d93282011-01-25 17:03:34 -08001609 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1610 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001611 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001612 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001613
1614 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1615 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001616 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001617 return File.FromLocalFile(name, prebuilt_path)
1618
Tao Bao32fcdab2018-10-12 10:30:39 -07001619 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001620
1621 if info_dict is None:
1622 info_dict = OPTIONS.info_dict
1623
1624 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001625 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1626 # for recovery.
1627 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1628 prebuilt_name != "boot.img" or
1629 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001630
Doug Zongker6f1d0312014-08-22 08:07:12 -07001631 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001632 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001633 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001634 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001635 if data:
1636 return File(name, data)
1637 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001638
Doug Zongkereef39442009-04-02 12:14:19 -07001639
Steve Mucklee1b10862019-07-10 10:49:37 -07001640def _BuildVendorBootImage(sourcedir, info_dict=None):
1641 """Build a vendor boot image from the specified sourcedir.
1642
1643 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1644 turn them into a vendor boot image.
1645
1646 Return the image data, or None if sourcedir does not appear to contains files
1647 for building the requested image.
1648 """
1649
1650 if info_dict is None:
1651 info_dict = OPTIONS.info_dict
1652
1653 img = tempfile.NamedTemporaryFile()
1654
J. Avila98cd4cc2020-06-10 20:09:10 +00001655 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1656 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001657
1658 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1659 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1660
1661 cmd = [mkbootimg]
1662
1663 fn = os.path.join(sourcedir, "dtb")
1664 if os.access(fn, os.F_OK):
1665 cmd.append("--dtb")
1666 cmd.append(fn)
1667
1668 fn = os.path.join(sourcedir, "vendor_cmdline")
1669 if os.access(fn, os.F_OK):
1670 cmd.append("--vendor_cmdline")
1671 cmd.append(open(fn).read().rstrip("\n"))
1672
1673 fn = os.path.join(sourcedir, "base")
1674 if os.access(fn, os.F_OK):
1675 cmd.append("--base")
1676 cmd.append(open(fn).read().rstrip("\n"))
1677
1678 fn = os.path.join(sourcedir, "pagesize")
1679 if os.access(fn, os.F_OK):
1680 cmd.append("--pagesize")
1681 cmd.append(open(fn).read().rstrip("\n"))
1682
1683 args = info_dict.get("mkbootimg_args")
1684 if args and args.strip():
1685 cmd.extend(shlex.split(args))
1686
1687 args = info_dict.get("mkbootimg_version_args")
1688 if args and args.strip():
1689 cmd.extend(shlex.split(args))
1690
1691 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1692 cmd.extend(["--vendor_boot", img.name])
1693
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.
Tao Baoda30cfa2017-12-01 16:19:46 -08001942 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001943
Tom Cherryd14b8952018-08-09 14:26:00 -07001944 # Special handling another case, where files not under /system
1945 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001946 if which == 'system' and not arcname.startswith('SYSTEM'):
1947 arcname = 'ROOT/' + arcname
1948
1949 assert arcname in input_zip.namelist(), \
1950 "Failed to find the ZIP entry for {}".format(entry)
1951
Tao Baoc765cca2018-01-31 17:32:40 -08001952 info = input_zip.getinfo(arcname)
1953 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001954
1955 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001956 # image, check the original block list to determine its completeness. Note
1957 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001958 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001959 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001960
Tao Baoc765cca2018-01-31 17:32:40 -08001961 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1962 ranges.extra['incomplete'] = True
1963
1964 return image
1965
1966
Doug Zongkereef39442009-04-02 12:14:19 -07001967def GetKeyPasswords(keylist):
1968 """Given a list of keys, prompt the user to enter passwords for
1969 those which require them. Return a {key: password} dict. password
1970 will be None if the key has no password."""
1971
Doug Zongker8ce7c252009-05-22 13:34:54 -07001972 no_passwords = []
1973 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001974 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001975 devnull = open("/dev/null", "w+b")
1976 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001977 # We don't need a password for things that aren't really keys.
1978 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001979 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001980 continue
1981
T.R. Fullhart37e10522013-03-18 10:31:26 -07001982 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001983 "-inform", "DER", "-nocrypt"],
1984 stdin=devnull.fileno(),
1985 stdout=devnull.fileno(),
1986 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001987 p.communicate()
1988 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001989 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001990 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001991 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001992 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1993 "-inform", "DER", "-passin", "pass:"],
1994 stdin=devnull.fileno(),
1995 stdout=devnull.fileno(),
1996 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001997 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001998 if p.returncode == 0:
1999 # Encrypted key with empty string as password.
2000 key_passwords[k] = ''
2001 elif stderr.startswith('Error decrypting key'):
2002 # Definitely encrypted key.
2003 # It would have said "Error reading key" if it didn't parse correctly.
2004 need_passwords.append(k)
2005 else:
2006 # Potentially, a type of key that openssl doesn't understand.
2007 # We'll let the routines in signapk.jar handle it.
2008 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002009 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002010
T.R. Fullhart37e10522013-03-18 10:31:26 -07002011 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002012 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002013 return key_passwords
2014
2015
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002016def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002017 """Gets the minSdkVersion declared in the APK.
2018
changho.shin0f125362019-07-08 10:59:00 +09002019 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002020 This can be both a decimal number (API Level) or a codename.
2021
2022 Args:
2023 apk_name: The APK filename.
2024
2025 Returns:
2026 The parsed SDK version string.
2027
2028 Raises:
2029 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002030 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002031 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002032 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002033 stderr=subprocess.PIPE)
2034 stdoutdata, stderrdata = proc.communicate()
2035 if proc.returncode != 0:
2036 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002037 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002038 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002039
Tao Baof47bf0f2018-03-21 23:28:51 -07002040 for line in stdoutdata.split("\n"):
2041 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002042 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2043 if m:
2044 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002045 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002046
2047
2048def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002049 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002050
Tao Baof47bf0f2018-03-21 23:28:51 -07002051 If minSdkVersion is set to a codename, it is translated to a number using the
2052 provided map.
2053
2054 Args:
2055 apk_name: The APK filename.
2056
2057 Returns:
2058 The parsed SDK version number.
2059
2060 Raises:
2061 ExternalError: On failing to get the min SDK version number.
2062 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002063 version = GetMinSdkVersion(apk_name)
2064 try:
2065 return int(version)
2066 except ValueError:
2067 # Not a decimal number. Codename?
2068 if version in codename_to_api_level_map:
2069 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002070 raise ExternalError(
2071 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2072 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002073
2074
2075def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002076 codename_to_api_level_map=None, whole_file=False,
2077 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002078 """Sign the input_name zip/jar/apk, producing output_name. Use the
2079 given key and password (the latter may be None if the key does not
2080 have a password.
2081
Doug Zongker951495f2009-08-14 12:44:19 -07002082 If whole_file is true, use the "-w" option to SignApk to embed a
2083 signature that covers the whole file in the archive comment of the
2084 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002085
2086 min_api_level is the API Level (int) of the oldest platform this file may end
2087 up on. If not specified for an APK, the API Level is obtained by interpreting
2088 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2089
2090 codename_to_api_level_map is needed to translate the codename which may be
2091 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002092
2093 Caller may optionally specify extra args to be passed to SignApk, which
2094 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002095 """
Tao Bao76def242017-11-21 09:25:31 -08002096 if codename_to_api_level_map is None:
2097 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002098 if extra_signapk_args is None:
2099 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002100
Alex Klyubin9667b182015-12-10 13:38:50 -08002101 java_library_path = os.path.join(
2102 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2103
Tao Baoe95540e2016-11-08 12:08:53 -08002104 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2105 ["-Djava.library.path=" + java_library_path,
2106 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002107 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002108 if whole_file:
2109 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002110
2111 min_sdk_version = min_api_level
2112 if min_sdk_version is None:
2113 if not whole_file:
2114 min_sdk_version = GetMinSdkVersionInt(
2115 input_name, codename_to_api_level_map)
2116 if min_sdk_version is not None:
2117 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2118
T.R. Fullhart37e10522013-03-18 10:31:26 -07002119 cmd.extend([key + OPTIONS.public_key_suffix,
2120 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002121 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002122
Tao Bao73dd4f42018-10-04 16:25:33 -07002123 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002124 if password is not None:
2125 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002126 stdoutdata, _ = proc.communicate(password)
2127 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002128 raise ExternalError(
2129 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002130 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002131
Doug Zongkereef39442009-04-02 12:14:19 -07002132
Doug Zongker37974732010-09-16 17:44:38 -07002133def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002134 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002135
Tao Bao9dd909e2017-11-14 11:27:32 -08002136 For non-AVB images, raise exception if the data is too big. Print a warning
2137 if the data is nearing the maximum size.
2138
2139 For AVB images, the actual image size should be identical to the limit.
2140
2141 Args:
2142 data: A string that contains all the data for the partition.
2143 target: The partition name. The ".img" suffix is optional.
2144 info_dict: The dict to be looked up for relevant info.
2145 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002146 if target.endswith(".img"):
2147 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002148 mount_point = "/" + target
2149
Ying Wangf8824af2014-06-03 14:07:27 -07002150 fs_type = None
2151 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002152 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002153 if mount_point == "/userdata":
2154 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002155 p = info_dict["fstab"][mount_point]
2156 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002157 device = p.device
2158 if "/" in device:
2159 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002160 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002161 if not fs_type or not limit:
2162 return
Doug Zongkereef39442009-04-02 12:14:19 -07002163
Andrew Boie0f9aec82012-02-14 09:32:52 -08002164 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002165 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2166 # path.
2167 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2168 if size != limit:
2169 raise ExternalError(
2170 "Mismatching image size for %s: expected %d actual %d" % (
2171 target, limit, size))
2172 else:
2173 pct = float(size) * 100.0 / limit
2174 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2175 if pct >= 99.0:
2176 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002177
2178 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002179 logger.warning("\n WARNING: %s\n", msg)
2180 else:
2181 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002182
2183
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002184def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002185 """Parses the APK certs info from a given target-files zip.
2186
2187 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2188 tuple with the following elements: (1) a dictionary that maps packages to
2189 certs (based on the "certificate" and "private_key" attributes in the file;
2190 (2) a string representing the extension of compressed APKs in the target files
2191 (e.g ".gz", ".bro").
2192
2193 Args:
2194 tf_zip: The input target_files ZipFile (already open).
2195
2196 Returns:
2197 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2198 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2199 no compressed APKs.
2200 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002201 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002202 compressed_extension = None
2203
Tao Bao0f990332017-09-08 19:02:54 -07002204 # META/apkcerts.txt contains the info for _all_ the packages known at build
2205 # time. Filter out the ones that are not installed.
2206 installed_files = set()
2207 for name in tf_zip.namelist():
2208 basename = os.path.basename(name)
2209 if basename:
2210 installed_files.add(basename)
2211
Tao Baoda30cfa2017-12-01 16:19:46 -08002212 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002213 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002214 if not line:
2215 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002216 m = re.match(
2217 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002218 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2219 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002220 line)
2221 if not m:
2222 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002223
Tao Bao818ddf52018-01-05 11:17:34 -08002224 matches = m.groupdict()
2225 cert = matches["CERT"]
2226 privkey = matches["PRIVKEY"]
2227 name = matches["NAME"]
2228 this_compressed_extension = matches["COMPRESSED"]
2229
2230 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2231 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2232 if cert in SPECIAL_CERT_STRINGS and not privkey:
2233 certmap[name] = cert
2234 elif (cert.endswith(OPTIONS.public_key_suffix) and
2235 privkey.endswith(OPTIONS.private_key_suffix) and
2236 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2237 certmap[name] = cert[:-public_key_suffix_len]
2238 else:
2239 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2240
2241 if not this_compressed_extension:
2242 continue
2243
2244 # Only count the installed files.
2245 filename = name + '.' + this_compressed_extension
2246 if filename not in installed_files:
2247 continue
2248
2249 # Make sure that all the values in the compression map have the same
2250 # extension. We don't support multiple compression methods in the same
2251 # system image.
2252 if compressed_extension:
2253 if this_compressed_extension != compressed_extension:
2254 raise ValueError(
2255 "Multiple compressed extensions: {} vs {}".format(
2256 compressed_extension, this_compressed_extension))
2257 else:
2258 compressed_extension = this_compressed_extension
2259
2260 return (certmap,
2261 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002262
2263
Doug Zongkereef39442009-04-02 12:14:19 -07002264COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002265Global options
2266
2267 -p (--path) <dir>
2268 Prepend <dir>/bin to the list of places to search for binaries run by this
2269 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002270
Doug Zongker05d3dea2009-06-22 11:32:31 -07002271 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002272 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002273
Tao Bao30df8b42018-04-23 15:32:53 -07002274 -x (--extra) <key=value>
2275 Add a key/value pair to the 'extras' dict, which device-specific extension
2276 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002277
Doug Zongkereef39442009-04-02 12:14:19 -07002278 -v (--verbose)
2279 Show command lines being executed.
2280
2281 -h (--help)
2282 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002283
2284 --logfile <file>
2285 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002286"""
2287
Kelvin Zhang0876c412020-06-23 15:06:58 -04002288
Doug Zongkereef39442009-04-02 12:14:19 -07002289def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002290 print(docstring.rstrip("\n"))
2291 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002292
2293
2294def ParseOptions(argv,
2295 docstring,
2296 extra_opts="", extra_long_opts=(),
2297 extra_option_handler=None):
2298 """Parse the options in argv and return any arguments that aren't
2299 flags. docstring is the calling module's docstring, to be displayed
2300 for errors and -h. extra_opts and extra_long_opts are for flags
2301 defined by the caller, which are processed by passing them to
2302 extra_option_handler."""
2303
2304 try:
2305 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002306 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002307 ["help", "verbose", "path=", "signapk_path=",
2308 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002309 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002310 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2311 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002312 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2313 "aftl_key_path=", "aftl_manufacturer_key_path=",
2314 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002315 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002316 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002317 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002318 sys.exit(2)
2319
Doug Zongkereef39442009-04-02 12:14:19 -07002320 for o, a in opts:
2321 if o in ("-h", "--help"):
2322 Usage(docstring)
2323 sys.exit()
2324 elif o in ("-v", "--verbose"):
2325 OPTIONS.verbose = True
2326 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002327 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002328 elif o in ("--signapk_path",):
2329 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002330 elif o in ("--signapk_shared_library_path",):
2331 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002332 elif o in ("--extra_signapk_args",):
2333 OPTIONS.extra_signapk_args = shlex.split(a)
2334 elif o in ("--java_path",):
2335 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002336 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002337 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002338 elif o in ("--android_jar_path",):
2339 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002340 elif o in ("--public_key_suffix",):
2341 OPTIONS.public_key_suffix = a
2342 elif o in ("--private_key_suffix",):
2343 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002344 elif o in ("--boot_signer_path",):
2345 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002346 elif o in ("--boot_signer_args",):
2347 OPTIONS.boot_signer_args = shlex.split(a)
2348 elif o in ("--verity_signer_path",):
2349 OPTIONS.verity_signer_path = a
2350 elif o in ("--verity_signer_args",):
2351 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002352 elif o in ("--aftl_tool_path",):
2353 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002354 elif o in ("--aftl_server",):
2355 OPTIONS.aftl_server = a
2356 elif o in ("--aftl_key_path",):
2357 OPTIONS.aftl_key_path = a
2358 elif o in ("--aftl_manufacturer_key_path",):
2359 OPTIONS.aftl_manufacturer_key_path = a
2360 elif o in ("--aftl_signer_helper",):
2361 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002362 elif o in ("-s", "--device_specific"):
2363 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002364 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002365 key, value = a.split("=", 1)
2366 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002367 elif o in ("--logfile",):
2368 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002369 else:
2370 if extra_option_handler is None or not extra_option_handler(o, a):
2371 assert False, "unknown option \"%s\"" % (o,)
2372
Doug Zongker85448772014-09-09 14:59:20 -07002373 if OPTIONS.search_path:
2374 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2375 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002376
2377 return args
2378
2379
Tao Bao4c851b12016-09-19 13:54:38 -07002380def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002381 """Make a temp file and add it to the list of things to be deleted
2382 when Cleanup() is called. Return the filename."""
2383 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2384 os.close(fd)
2385 OPTIONS.tempfiles.append(fn)
2386 return fn
2387
2388
Tao Bao1c830bf2017-12-25 10:43:47 -08002389def MakeTempDir(prefix='tmp', suffix=''):
2390 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2391
2392 Returns:
2393 The absolute pathname of the new directory.
2394 """
2395 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2396 OPTIONS.tempfiles.append(dir_name)
2397 return dir_name
2398
2399
Doug Zongkereef39442009-04-02 12:14:19 -07002400def Cleanup():
2401 for i in OPTIONS.tempfiles:
2402 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002403 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002404 else:
2405 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002406 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002407
2408
2409class PasswordManager(object):
2410 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002411 self.editor = os.getenv("EDITOR")
2412 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002413
2414 def GetPasswords(self, items):
2415 """Get passwords corresponding to each string in 'items',
2416 returning a dict. (The dict may have keys in addition to the
2417 values in 'items'.)
2418
2419 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2420 user edit that file to add more needed passwords. If no editor is
2421 available, or $ANDROID_PW_FILE isn't define, prompts the user
2422 interactively in the ordinary way.
2423 """
2424
2425 current = self.ReadFile()
2426
2427 first = True
2428 while True:
2429 missing = []
2430 for i in items:
2431 if i not in current or not current[i]:
2432 missing.append(i)
2433 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002434 if not missing:
2435 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002436
2437 for i in missing:
2438 current[i] = ""
2439
2440 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002441 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002442 if sys.version_info[0] >= 3:
2443 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002444 answer = raw_input("try to edit again? [y]> ").strip()
2445 if answer and answer[0] not in 'yY':
2446 raise RuntimeError("key passwords unavailable")
2447 first = False
2448
2449 current = self.UpdateAndReadFile(current)
2450
Kelvin Zhang0876c412020-06-23 15:06:58 -04002451 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002452 """Prompt the user to enter a value (password) for each key in
2453 'current' whose value is fales. Returns a new dict with all the
2454 values.
2455 """
2456 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002457 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002458 if v:
2459 result[k] = v
2460 else:
2461 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002462 result[k] = getpass.getpass(
2463 "Enter password for %s key> " % k).strip()
2464 if result[k]:
2465 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002466 return result
2467
2468 def UpdateAndReadFile(self, current):
2469 if not self.editor or not self.pwfile:
2470 return self.PromptResult(current)
2471
2472 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002473 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002474 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2475 f.write("# (Additional spaces are harmless.)\n\n")
2476
2477 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002478 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002479 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002480 f.write("[[[ %s ]]] %s\n" % (v, k))
2481 if not v and first_line is None:
2482 # position cursor on first line with no password.
2483 first_line = i + 4
2484 f.close()
2485
Tao Bao986ee862018-10-04 15:46:16 -07002486 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002487
2488 return self.ReadFile()
2489
2490 def ReadFile(self):
2491 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002492 if self.pwfile is None:
2493 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002494 try:
2495 f = open(self.pwfile, "r")
2496 for line in f:
2497 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002498 if not line or line[0] == '#':
2499 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002500 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2501 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002502 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002503 else:
2504 result[m.group(2)] = m.group(1)
2505 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002506 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002507 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002508 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002509 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002510
2511
Dan Albert8e0178d2015-01-27 15:53:15 -08002512def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2513 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002514
2515 # http://b/18015246
2516 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2517 # for files larger than 2GiB. We can work around this by adjusting their
2518 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2519 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2520 # it isn't clear to me exactly what circumstances cause this).
2521 # `zipfile.write()` must be used directly to work around this.
2522 #
2523 # This mess can be avoided if we port to python3.
2524 saved_zip64_limit = zipfile.ZIP64_LIMIT
2525 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2526
2527 if compress_type is None:
2528 compress_type = zip_file.compression
2529 if arcname is None:
2530 arcname = filename
2531
2532 saved_stat = os.stat(filename)
2533
2534 try:
2535 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2536 # file to be zipped and reset it when we're done.
2537 os.chmod(filename, perms)
2538
2539 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002540 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2541 # intentional. zip stores datetimes in local time without a time zone
2542 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2543 # in the zip archive.
2544 local_epoch = datetime.datetime.fromtimestamp(0)
2545 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002546 os.utime(filename, (timestamp, timestamp))
2547
2548 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2549 finally:
2550 os.chmod(filename, saved_stat.st_mode)
2551 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2552 zipfile.ZIP64_LIMIT = saved_zip64_limit
2553
2554
Tao Bao58c1b962015-05-20 09:32:18 -07002555def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002556 compress_type=None):
2557 """Wrap zipfile.writestr() function to work around the zip64 limit.
2558
2559 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2560 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2561 when calling crc32(bytes).
2562
2563 But it still works fine to write a shorter string into a large zip file.
2564 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2565 when we know the string won't be too long.
2566 """
2567
2568 saved_zip64_limit = zipfile.ZIP64_LIMIT
2569 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2570
2571 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2572 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002573 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002574 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002575 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002576 else:
Tao Baof3282b42015-04-01 11:21:55 -07002577 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002578 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2579 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2580 # such a case (since
2581 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2582 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2583 # permission bits. We follow the logic in Python 3 to get consistent
2584 # behavior between using the two versions.
2585 if not zinfo.external_attr:
2586 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002587
2588 # If compress_type is given, it overrides the value in zinfo.
2589 if compress_type is not None:
2590 zinfo.compress_type = compress_type
2591
Tao Bao58c1b962015-05-20 09:32:18 -07002592 # If perms is given, it has a priority.
2593 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002594 # If perms doesn't set the file type, mark it as a regular file.
2595 if perms & 0o770000 == 0:
2596 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002597 zinfo.external_attr = perms << 16
2598
Tao Baof3282b42015-04-01 11:21:55 -07002599 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002600 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2601
Dan Albert8b72aef2015-03-23 19:13:21 -07002602 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002603 zipfile.ZIP64_LIMIT = saved_zip64_limit
2604
2605
Tao Bao89d7ab22017-12-14 17:05:33 -08002606def ZipDelete(zip_filename, entries):
2607 """Deletes entries from a ZIP file.
2608
2609 Since deleting entries from a ZIP file is not supported, it shells out to
2610 'zip -d'.
2611
2612 Args:
2613 zip_filename: The name of the ZIP file.
2614 entries: The name of the entry, or the list of names to be deleted.
2615
2616 Raises:
2617 AssertionError: In case of non-zero return from 'zip'.
2618 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002619 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002620 entries = [entries]
2621 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002622 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002623
2624
Tao Baof3282b42015-04-01 11:21:55 -07002625def ZipClose(zip_file):
2626 # http://b/18015246
2627 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2628 # central directory.
2629 saved_zip64_limit = zipfile.ZIP64_LIMIT
2630 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2631
2632 zip_file.close()
2633
2634 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002635
2636
2637class DeviceSpecificParams(object):
2638 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002639
Doug Zongker05d3dea2009-06-22 11:32:31 -07002640 def __init__(self, **kwargs):
2641 """Keyword arguments to the constructor become attributes of this
2642 object, which is passed to all functions in the device-specific
2643 module."""
Tao Bao38884282019-07-10 22:20:56 -07002644 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002645 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002646 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002647
2648 if self.module is None:
2649 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002650 if not path:
2651 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002652 try:
2653 if os.path.isdir(path):
2654 info = imp.find_module("releasetools", [path])
2655 else:
2656 d, f = os.path.split(path)
2657 b, x = os.path.splitext(f)
2658 if x == ".py":
2659 f = b
2660 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002661 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002662 self.module = imp.load_module("device_specific", *info)
2663 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002664 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002665
2666 def _DoCall(self, function_name, *args, **kwargs):
2667 """Call the named function in the device-specific module, passing
2668 the given args and kwargs. The first argument to the call will be
2669 the DeviceSpecific object itself. If there is no module, or the
2670 module does not define the function, return the value of the
2671 'default' kwarg (which itself defaults to None)."""
2672 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002673 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002674 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2675
2676 def FullOTA_Assertions(self):
2677 """Called after emitting the block of assertions at the top of a
2678 full OTA package. Implementations can add whatever additional
2679 assertions they like."""
2680 return self._DoCall("FullOTA_Assertions")
2681
Doug Zongkere5ff5902012-01-17 10:55:37 -08002682 def FullOTA_InstallBegin(self):
2683 """Called at the start of full OTA installation."""
2684 return self._DoCall("FullOTA_InstallBegin")
2685
Yifan Hong10c530d2018-12-27 17:34:18 -08002686 def FullOTA_GetBlockDifferences(self):
2687 """Called during full OTA installation and verification.
2688 Implementation should return a list of BlockDifference objects describing
2689 the update on each additional partitions.
2690 """
2691 return self._DoCall("FullOTA_GetBlockDifferences")
2692
Doug Zongker05d3dea2009-06-22 11:32:31 -07002693 def FullOTA_InstallEnd(self):
2694 """Called at the end of full OTA installation; typically this is
2695 used to install the image for the device's baseband processor."""
2696 return self._DoCall("FullOTA_InstallEnd")
2697
2698 def IncrementalOTA_Assertions(self):
2699 """Called after emitting the block of assertions at the top of an
2700 incremental OTA package. Implementations can add whatever
2701 additional assertions they like."""
2702 return self._DoCall("IncrementalOTA_Assertions")
2703
Doug Zongkere5ff5902012-01-17 10:55:37 -08002704 def IncrementalOTA_VerifyBegin(self):
2705 """Called at the start of the verification phase of incremental
2706 OTA installation; additional checks can be placed here to abort
2707 the script before any changes are made."""
2708 return self._DoCall("IncrementalOTA_VerifyBegin")
2709
Doug Zongker05d3dea2009-06-22 11:32:31 -07002710 def IncrementalOTA_VerifyEnd(self):
2711 """Called at the end of the verification phase of incremental OTA
2712 installation; additional checks can be placed here to abort the
2713 script before any changes are made."""
2714 return self._DoCall("IncrementalOTA_VerifyEnd")
2715
Doug Zongkere5ff5902012-01-17 10:55:37 -08002716 def IncrementalOTA_InstallBegin(self):
2717 """Called at the start of incremental OTA installation (after
2718 verification is complete)."""
2719 return self._DoCall("IncrementalOTA_InstallBegin")
2720
Yifan Hong10c530d2018-12-27 17:34:18 -08002721 def IncrementalOTA_GetBlockDifferences(self):
2722 """Called during incremental OTA installation and verification.
2723 Implementation should return a list of BlockDifference objects describing
2724 the update on each additional partitions.
2725 """
2726 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2727
Doug Zongker05d3dea2009-06-22 11:32:31 -07002728 def IncrementalOTA_InstallEnd(self):
2729 """Called at the end of incremental OTA installation; typically
2730 this is used to install the image for the device's baseband
2731 processor."""
2732 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002733
Tao Bao9bc6bb22015-11-09 16:58:28 -08002734 def VerifyOTA_Assertions(self):
2735 return self._DoCall("VerifyOTA_Assertions")
2736
Tao Bao76def242017-11-21 09:25:31 -08002737
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002738class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002739 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002740 self.name = name
2741 self.data = data
2742 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002743 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002744 self.sha1 = sha1(data).hexdigest()
2745
2746 @classmethod
2747 def FromLocalFile(cls, name, diskname):
2748 f = open(diskname, "rb")
2749 data = f.read()
2750 f.close()
2751 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002752
2753 def WriteToTemp(self):
2754 t = tempfile.NamedTemporaryFile()
2755 t.write(self.data)
2756 t.flush()
2757 return t
2758
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002759 def WriteToDir(self, d):
2760 with open(os.path.join(d, self.name), "wb") as fp:
2761 fp.write(self.data)
2762
Geremy Condra36bd3652014-02-06 19:45:10 -08002763 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002764 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002765
Tao Bao76def242017-11-21 09:25:31 -08002766
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002767DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002768 ".gz": "imgdiff",
2769 ".zip": ["imgdiff", "-z"],
2770 ".jar": ["imgdiff", "-z"],
2771 ".apk": ["imgdiff", "-z"],
2772 ".img": "imgdiff",
2773}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002774
Tao Bao76def242017-11-21 09:25:31 -08002775
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002776class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002777 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002778 self.tf = tf
2779 self.sf = sf
2780 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002781 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002782
2783 def ComputePatch(self):
2784 """Compute the patch (as a string of data) needed to turn sf into
2785 tf. Returns the same tuple as GetPatch()."""
2786
2787 tf = self.tf
2788 sf = self.sf
2789
Doug Zongker24cd2802012-08-14 16:36:15 -07002790 if self.diff_program:
2791 diff_program = self.diff_program
2792 else:
2793 ext = os.path.splitext(tf.name)[1]
2794 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002795
2796 ttemp = tf.WriteToTemp()
2797 stemp = sf.WriteToTemp()
2798
2799 ext = os.path.splitext(tf.name)[1]
2800
2801 try:
2802 ptemp = tempfile.NamedTemporaryFile()
2803 if isinstance(diff_program, list):
2804 cmd = copy.copy(diff_program)
2805 else:
2806 cmd = [diff_program]
2807 cmd.append(stemp.name)
2808 cmd.append(ttemp.name)
2809 cmd.append(ptemp.name)
2810 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002811 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002812
Doug Zongkerf8340082014-08-05 10:39:37 -07002813 def run():
2814 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002815 if e:
2816 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002817 th = threading.Thread(target=run)
2818 th.start()
2819 th.join(timeout=300) # 5 mins
2820 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002821 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002822 p.terminate()
2823 th.join(5)
2824 if th.is_alive():
2825 p.kill()
2826 th.join()
2827
Tianjie Xua2a9f992018-01-05 15:15:54 -08002828 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002829 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002830 self.patch = None
2831 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002832 diff = ptemp.read()
2833 finally:
2834 ptemp.close()
2835 stemp.close()
2836 ttemp.close()
2837
2838 self.patch = diff
2839 return self.tf, self.sf, self.patch
2840
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002841 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002842 """Returns a tuple of (target_file, source_file, patch_data).
2843
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002844 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002845 computing the patch failed.
2846 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002847 return self.tf, self.sf, self.patch
2848
2849
2850def ComputeDifferences(diffs):
2851 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002852 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002853
2854 # Do the largest files first, to try and reduce the long-pole effect.
2855 by_size = [(i.tf.size, i) for i in diffs]
2856 by_size.sort(reverse=True)
2857 by_size = [i[1] for i in by_size]
2858
2859 lock = threading.Lock()
2860 diff_iter = iter(by_size) # accessed under lock
2861
2862 def worker():
2863 try:
2864 lock.acquire()
2865 for d in diff_iter:
2866 lock.release()
2867 start = time.time()
2868 d.ComputePatch()
2869 dur = time.time() - start
2870 lock.acquire()
2871
2872 tf, sf, patch = d.GetPatch()
2873 if sf.name == tf.name:
2874 name = tf.name
2875 else:
2876 name = "%s (%s)" % (tf.name, sf.name)
2877 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002878 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002879 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002880 logger.info(
2881 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2882 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002883 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002884 except Exception:
2885 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002886 raise
2887
2888 # start worker threads; wait for them all to finish.
2889 threads = [threading.Thread(target=worker)
2890 for i in range(OPTIONS.worker_threads)]
2891 for th in threads:
2892 th.start()
2893 while threads:
2894 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002895
2896
Dan Albert8b72aef2015-03-23 19:13:21 -07002897class BlockDifference(object):
2898 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002899 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002900 self.tgt = tgt
2901 self.src = src
2902 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002903 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002904 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002905
Tao Baodd2a5892015-03-12 12:32:37 -07002906 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002907 version = max(
2908 int(i) for i in
2909 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002910 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002911 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002912
Tianjie Xu41976c72019-07-03 13:57:01 -07002913 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2914 version=self.version,
2915 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002916 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002917 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002918 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002919 self.touched_src_ranges = b.touched_src_ranges
2920 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002921
Yifan Hong10c530d2018-12-27 17:34:18 -08002922 # On devices with dynamic partitions, for new partitions,
2923 # src is None but OPTIONS.source_info_dict is not.
2924 if OPTIONS.source_info_dict is None:
2925 is_dynamic_build = OPTIONS.info_dict.get(
2926 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002927 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002928 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002929 is_dynamic_build = OPTIONS.source_info_dict.get(
2930 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002931 is_dynamic_source = partition in shlex.split(
2932 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002933
Yifan Hongbb2658d2019-01-25 12:30:58 -08002934 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002935 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2936
Yifan Hongbb2658d2019-01-25 12:30:58 -08002937 # For dynamic partitions builds, check partition list in both source
2938 # and target build because new partitions may be added, and existing
2939 # partitions may be removed.
2940 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2941
Yifan Hong10c530d2018-12-27 17:34:18 -08002942 if is_dynamic:
2943 self.device = 'map_partition("%s")' % partition
2944 else:
2945 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002946 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2947 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002948 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002949 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2950 OPTIONS.source_info_dict)
2951 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002952
Tao Baod8d14be2016-02-04 14:26:02 -08002953 @property
2954 def required_cache(self):
2955 return self._required_cache
2956
Tao Bao76def242017-11-21 09:25:31 -08002957 def WriteScript(self, script, output_zip, progress=None,
2958 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002959 if not self.src:
2960 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002961 script.Print("Patching %s image unconditionally..." % (self.partition,))
2962 else:
2963 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002964
Dan Albert8b72aef2015-03-23 19:13:21 -07002965 if progress:
2966 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002967 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002968
2969 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002970 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002971
Tao Bao9bc6bb22015-11-09 16:58:28 -08002972 def WriteStrictVerifyScript(self, script):
2973 """Verify all the blocks in the care_map, including clobbered blocks.
2974
2975 This differs from the WriteVerifyScript() function: a) it prints different
2976 error messages; b) it doesn't allow half-way updated images to pass the
2977 verification."""
2978
2979 partition = self.partition
2980 script.Print("Verifying %s..." % (partition,))
2981 ranges = self.tgt.care_map
2982 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002983 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002984 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2985 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002986 self.device, ranges_str,
2987 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002988 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002989 script.AppendExtra("")
2990
Tao Baod522bdc2016-04-12 15:53:16 -07002991 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002992 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002993
2994 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002995 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002996 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002997
2998 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002999 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003000 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003001 ranges = self.touched_src_ranges
3002 expected_sha1 = self.touched_src_sha1
3003 else:
3004 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3005 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003006
3007 # No blocks to be checked, skipping.
3008 if not ranges:
3009 return
3010
Tao Bao5ece99d2015-05-12 11:42:31 -07003011 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003012 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003013 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003014 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3015 '"%s.patch.dat")) then' % (
3016 self.device, ranges_str, expected_sha1,
3017 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003018 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003019 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003020
Tianjie Xufc3422a2015-12-15 11:53:59 -08003021 if self.version >= 4:
3022
3023 # Bug: 21124327
3024 # When generating incrementals for the system and vendor partitions in
3025 # version 4 or newer, explicitly check the first block (which contains
3026 # the superblock) of the partition to see if it's what we expect. If
3027 # this check fails, give an explicit log message about the partition
3028 # having been remounted R/W (the most likely explanation).
3029 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003030 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003031
3032 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003033 if partition == "system":
3034 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3035 else:
3036 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003037 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003038 'ifelse (block_image_recover({device}, "{ranges}") && '
3039 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003040 'package_extract_file("{partition}.transfer.list"), '
3041 '"{partition}.new.dat", "{partition}.patch.dat"), '
3042 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003043 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003044 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003045 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003046
Tao Baodd2a5892015-03-12 12:32:37 -07003047 # Abort the OTA update. Note that the incremental OTA cannot be applied
3048 # even if it may match the checksum of the target partition.
3049 # a) If version < 3, operations like move and erase will make changes
3050 # unconditionally and damage the partition.
3051 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003052 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003053 if partition == "system":
3054 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3055 else:
3056 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3057 script.AppendExtra((
3058 'abort("E%d: %s partition has unexpected contents");\n'
3059 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003060
Yifan Hong10c530d2018-12-27 17:34:18 -08003061 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003062 partition = self.partition
3063 script.Print('Verifying the updated %s image...' % (partition,))
3064 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3065 ranges = self.tgt.care_map
3066 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003067 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003068 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003069 self.device, ranges_str,
3070 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003071
3072 # Bug: 20881595
3073 # Verify that extended blocks are really zeroed out.
3074 if self.tgt.extended:
3075 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003076 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003077 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003078 self.device, ranges_str,
3079 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003080 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003081 if partition == "system":
3082 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3083 else:
3084 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003085 script.AppendExtra(
3086 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003087 ' abort("E%d: %s partition has unexpected non-zero contents after '
3088 'OTA update");\n'
3089 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003090 else:
3091 script.Print('Verified the updated %s image.' % (partition,))
3092
Tianjie Xu209db462016-05-24 17:34:52 -07003093 if partition == "system":
3094 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3095 else:
3096 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3097
Tao Bao5fcaaef2015-06-01 13:40:49 -07003098 script.AppendExtra(
3099 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003100 ' abort("E%d: %s partition has unexpected contents after OTA '
3101 'update");\n'
3102 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003103
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003104 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003105 ZipWrite(output_zip,
3106 '{}.transfer.list'.format(self.path),
3107 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003108
Tao Bao76def242017-11-21 09:25:31 -08003109 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3110 # its size. Quailty 9 almost triples the compression time but doesn't
3111 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003112 # zip | brotli(quality 6) | brotli(quality 9)
3113 # compressed_size: 942M | 869M (~8% reduced) | 854M
3114 # compression_time: 75s | 265s | 719s
3115 # decompression_time: 15s | 25s | 25s
3116
3117 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003118 brotli_cmd = ['brotli', '--quality=6',
3119 '--output={}.new.dat.br'.format(self.path),
3120 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003121 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003122 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003123
3124 new_data_name = '{}.new.dat.br'.format(self.partition)
3125 ZipWrite(output_zip,
3126 '{}.new.dat.br'.format(self.path),
3127 new_data_name,
3128 compress_type=zipfile.ZIP_STORED)
3129 else:
3130 new_data_name = '{}.new.dat'.format(self.partition)
3131 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3132
Dan Albert8e0178d2015-01-27 15:53:15 -08003133 ZipWrite(output_zip,
3134 '{}.patch.dat'.format(self.path),
3135 '{}.patch.dat'.format(self.partition),
3136 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003137
Tianjie Xu209db462016-05-24 17:34:52 -07003138 if self.partition == "system":
3139 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3140 else:
3141 code = ErrorCode.VENDOR_UPDATE_FAILURE
3142
Yifan Hong10c530d2018-12-27 17:34:18 -08003143 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003144 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003145 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003146 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003147 device=self.device, partition=self.partition,
3148 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003149 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003150
Kelvin Zhang0876c412020-06-23 15:06:58 -04003151 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003152 data = source.ReadRangeSet(ranges)
3153 ctx = sha1()
3154
3155 for p in data:
3156 ctx.update(p)
3157
3158 return ctx.hexdigest()
3159
Kelvin Zhang0876c412020-06-23 15:06:58 -04003160 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003161 """Return the hash value for all zero blocks."""
3162 zero_block = '\x00' * 4096
3163 ctx = sha1()
3164 for _ in range(num_blocks):
3165 ctx.update(zero_block)
3166
3167 return ctx.hexdigest()
3168
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003169
Tianjie Xu41976c72019-07-03 13:57:01 -07003170# Expose these two classes to support vendor-specific scripts
3171DataImage = images.DataImage
3172EmptyImage = images.EmptyImage
3173
Tao Bao76def242017-11-21 09:25:31 -08003174
Doug Zongker96a57e72010-09-26 14:57:41 -07003175# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003176PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003177 "ext4": "EMMC",
3178 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003179 "f2fs": "EMMC",
3180 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003181}
Doug Zongker96a57e72010-09-26 14:57:41 -07003182
Kelvin Zhang0876c412020-06-23 15:06:58 -04003183
Yifan Hongbdb32012020-05-07 12:38:53 -07003184def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3185 """
3186 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3187 backwards compatibility. It aborts if the fstab entry has slotselect option
3188 (unless check_no_slot is explicitly set to False).
3189 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003190 fstab = info["fstab"]
3191 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003192 if check_no_slot:
3193 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003194 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003195 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3196 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003197 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003198
3199
Yifan Hongbdb32012020-05-07 12:38:53 -07003200def GetTypeAndDeviceExpr(mount_point, info):
3201 """
3202 Return the filesystem of the partition, and an edify expression that evaluates
3203 to the device at runtime.
3204 """
3205 fstab = info["fstab"]
3206 if fstab:
3207 p = fstab[mount_point]
3208 device_expr = '"%s"' % fstab[mount_point].device
3209 if p.slotselect:
3210 device_expr = 'add_slot_suffix(%s)' % device_expr
3211 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003212 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003213
3214
3215def GetEntryForDevice(fstab, device):
3216 """
3217 Returns:
3218 The first entry in fstab whose device is the given value.
3219 """
3220 if not fstab:
3221 return None
3222 for mount_point in fstab:
3223 if fstab[mount_point].device == device:
3224 return fstab[mount_point]
3225 return None
3226
Kelvin Zhang0876c412020-06-23 15:06:58 -04003227
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003228def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003229 """Parses and converts a PEM-encoded certificate into DER-encoded.
3230
3231 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3232
3233 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003234 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003235 """
3236 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003237 save = False
3238 for line in data.split("\n"):
3239 if "--END CERTIFICATE--" in line:
3240 break
3241 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003242 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003243 if "--BEGIN CERTIFICATE--" in line:
3244 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003245 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003246 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003247
Tao Bao04e1f012018-02-04 12:13:35 -08003248
3249def ExtractPublicKey(cert):
3250 """Extracts the public key (PEM-encoded) from the given certificate file.
3251
3252 Args:
3253 cert: The certificate filename.
3254
3255 Returns:
3256 The public key string.
3257
3258 Raises:
3259 AssertionError: On non-zero return from 'openssl'.
3260 """
3261 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3262 # While openssl 1.1 writes the key into the given filename followed by '-out',
3263 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3264 # stdout instead.
3265 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3266 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3267 pubkey, stderrdata = proc.communicate()
3268 assert proc.returncode == 0, \
3269 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3270 return pubkey
3271
3272
Tao Bao1ac886e2019-06-26 11:58:22 -07003273def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003274 """Extracts the AVB public key from the given public or private key.
3275
3276 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003277 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003278 key: The input key file, which should be PEM-encoded public or private key.
3279
3280 Returns:
3281 The path to the extracted AVB public key file.
3282 """
3283 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3284 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003285 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003286 return output
3287
3288
Doug Zongker412c02f2014-02-13 10:58:24 -08003289def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3290 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003291 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003292
Tao Bao6d5d6232018-03-09 17:04:42 -08003293 Most of the space in the boot and recovery images is just the kernel, which is
3294 identical for the two, so the resulting patch should be efficient. Add it to
3295 the output zip, along with a shell script that is run from init.rc on first
3296 boot to actually do the patching and install the new recovery image.
3297
3298 Args:
3299 input_dir: The top-level input directory of the target-files.zip.
3300 output_sink: The callback function that writes the result.
3301 recovery_img: File object for the recovery image.
3302 boot_img: File objects for the boot image.
3303 info_dict: A dict returned by common.LoadInfoDict() on the input
3304 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003305 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003306 if info_dict is None:
3307 info_dict = OPTIONS.info_dict
3308
Tao Bao6d5d6232018-03-09 17:04:42 -08003309 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003310 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3311
3312 if board_uses_vendorimage:
3313 # In this case, the output sink is rooted at VENDOR
3314 recovery_img_path = "etc/recovery.img"
3315 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3316 sh_dir = "bin"
3317 else:
3318 # In this case the output sink is rooted at SYSTEM
3319 recovery_img_path = "vendor/etc/recovery.img"
3320 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3321 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003322
Tao Baof2cffbd2015-07-22 12:33:18 -07003323 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003324 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003325
3326 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003327 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003328 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003329 # With system-root-image, boot and recovery images will have mismatching
3330 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3331 # to handle such a case.
3332 if system_root_image:
3333 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003334 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003335 assert not os.path.exists(path)
3336 else:
3337 diff_program = ["imgdiff"]
3338 if os.path.exists(path):
3339 diff_program.append("-b")
3340 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003341 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003342 else:
3343 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003344
3345 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3346 _, _, patch = d.ComputePatch()
3347 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003348
Dan Albertebb19aa2015-03-27 19:11:53 -07003349 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003350 # The following GetTypeAndDevice()s need to use the path in the target
3351 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003352 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3353 check_no_slot=False)
3354 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3355 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003356 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003357 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003358
Tao Baof2cffbd2015-07-22 12:33:18 -07003359 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003360
3361 # Note that we use /vendor to refer to the recovery resources. This will
3362 # work for a separate vendor partition mounted at /vendor or a
3363 # /system/vendor subdirectory on the system partition, for which init will
3364 # create a symlink from /vendor to /system/vendor.
3365
3366 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003367if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3368 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003369 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003370 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3371 log -t recovery "Installing new recovery image: succeeded" || \\
3372 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003373else
3374 log -t recovery "Recovery image already installed"
3375fi
3376""" % {'type': recovery_type,
3377 'device': recovery_device,
3378 'sha1': recovery_img.sha1,
3379 'size': recovery_img.size}
3380 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003381 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003382if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3383 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003384 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003385 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3386 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3387 log -t recovery "Installing new recovery image: succeeded" || \\
3388 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003389else
3390 log -t recovery "Recovery image already installed"
3391fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003392""" % {'boot_size': boot_img.size,
3393 'boot_sha1': boot_img.sha1,
3394 'recovery_size': recovery_img.size,
3395 'recovery_sha1': recovery_img.sha1,
3396 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003397 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003398 'recovery_type': recovery_type,
3399 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003400 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003401
Bill Peckhame868aec2019-09-17 17:06:47 -07003402 # The install script location moved from /system/etc to /system/bin in the L
3403 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3404 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003405
Tao Bao32fcdab2018-10-12 10:30:39 -07003406 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003407
Tao Baoda30cfa2017-12-01 16:19:46 -08003408 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003409
3410
3411class DynamicPartitionUpdate(object):
3412 def __init__(self, src_group=None, tgt_group=None, progress=None,
3413 block_difference=None):
3414 self.src_group = src_group
3415 self.tgt_group = tgt_group
3416 self.progress = progress
3417 self.block_difference = block_difference
3418
3419 @property
3420 def src_size(self):
3421 if not self.block_difference:
3422 return 0
3423 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3424
3425 @property
3426 def tgt_size(self):
3427 if not self.block_difference:
3428 return 0
3429 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3430
3431 @staticmethod
3432 def _GetSparseImageSize(img):
3433 if not img:
3434 return 0
3435 return img.blocksize * img.total_blocks
3436
3437
3438class DynamicGroupUpdate(object):
3439 def __init__(self, src_size=None, tgt_size=None):
3440 # None: group does not exist. 0: no size limits.
3441 self.src_size = src_size
3442 self.tgt_size = tgt_size
3443
3444
3445class DynamicPartitionsDifference(object):
3446 def __init__(self, info_dict, block_diffs, progress_dict=None,
3447 source_info_dict=None):
3448 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003449 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003450
3451 self._remove_all_before_apply = False
3452 if source_info_dict is None:
3453 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003454 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003455
Tao Baof1113e92019-06-18 12:10:14 -07003456 block_diff_dict = collections.OrderedDict(
3457 [(e.partition, e) for e in block_diffs])
3458
Yifan Hong10c530d2018-12-27 17:34:18 -08003459 assert len(block_diff_dict) == len(block_diffs), \
3460 "Duplicated BlockDifference object for {}".format(
3461 [partition for partition, count in
3462 collections.Counter(e.partition for e in block_diffs).items()
3463 if count > 1])
3464
Yifan Hong79997e52019-01-23 16:56:19 -08003465 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003466
3467 for p, block_diff in block_diff_dict.items():
3468 self._partition_updates[p] = DynamicPartitionUpdate()
3469 self._partition_updates[p].block_difference = block_diff
3470
3471 for p, progress in progress_dict.items():
3472 if p in self._partition_updates:
3473 self._partition_updates[p].progress = progress
3474
3475 tgt_groups = shlex.split(info_dict.get(
3476 "super_partition_groups", "").strip())
3477 src_groups = shlex.split(source_info_dict.get(
3478 "super_partition_groups", "").strip())
3479
3480 for g in tgt_groups:
3481 for p in shlex.split(info_dict.get(
3482 "super_%s_partition_list" % g, "").strip()):
3483 assert p in self._partition_updates, \
3484 "{} is in target super_{}_partition_list but no BlockDifference " \
3485 "object is provided.".format(p, g)
3486 self._partition_updates[p].tgt_group = g
3487
3488 for g in src_groups:
3489 for p in shlex.split(source_info_dict.get(
3490 "super_%s_partition_list" % g, "").strip()):
3491 assert p in self._partition_updates, \
3492 "{} is in source super_{}_partition_list but no BlockDifference " \
3493 "object is provided.".format(p, g)
3494 self._partition_updates[p].src_group = g
3495
Yifan Hong45433e42019-01-18 13:55:25 -08003496 target_dynamic_partitions = set(shlex.split(info_dict.get(
3497 "dynamic_partition_list", "").strip()))
3498 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3499 if u.tgt_size)
3500 assert block_diffs_with_target == target_dynamic_partitions, \
3501 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3502 list(target_dynamic_partitions), list(block_diffs_with_target))
3503
3504 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3505 "dynamic_partition_list", "").strip()))
3506 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3507 if u.src_size)
3508 assert block_diffs_with_source == source_dynamic_partitions, \
3509 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3510 list(source_dynamic_partitions), list(block_diffs_with_source))
3511
Yifan Hong10c530d2018-12-27 17:34:18 -08003512 if self._partition_updates:
3513 logger.info("Updating dynamic partitions %s",
3514 self._partition_updates.keys())
3515
Yifan Hong79997e52019-01-23 16:56:19 -08003516 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003517
3518 for g in tgt_groups:
3519 self._group_updates[g] = DynamicGroupUpdate()
3520 self._group_updates[g].tgt_size = int(info_dict.get(
3521 "super_%s_group_size" % g, "0").strip())
3522
3523 for g in src_groups:
3524 if g not in self._group_updates:
3525 self._group_updates[g] = DynamicGroupUpdate()
3526 self._group_updates[g].src_size = int(source_info_dict.get(
3527 "super_%s_group_size" % g, "0").strip())
3528
3529 self._Compute()
3530
3531 def WriteScript(self, script, output_zip, write_verify_script=False):
3532 script.Comment('--- Start patching dynamic partitions ---')
3533 for p, u in self._partition_updates.items():
3534 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3535 script.Comment('Patch partition %s' % p)
3536 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3537 write_verify_script=False)
3538
3539 op_list_path = MakeTempFile()
3540 with open(op_list_path, 'w') as f:
3541 for line in self._op_list:
3542 f.write('{}\n'.format(line))
3543
3544 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3545
3546 script.Comment('Update dynamic partition metadata')
3547 script.AppendExtra('assert(update_dynamic_partitions('
3548 'package_extract_file("dynamic_partitions_op_list")));')
3549
3550 if write_verify_script:
3551 for p, u in self._partition_updates.items():
3552 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3553 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003554 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003555
3556 for p, u in self._partition_updates.items():
3557 if u.tgt_size and u.src_size <= u.tgt_size:
3558 script.Comment('Patch partition %s' % p)
3559 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3560 write_verify_script=write_verify_script)
3561 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003562 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003563
3564 script.Comment('--- End patching dynamic partitions ---')
3565
3566 def _Compute(self):
3567 self._op_list = list()
3568
3569 def append(line):
3570 self._op_list.append(line)
3571
3572 def comment(line):
3573 self._op_list.append("# %s" % line)
3574
3575 if self._remove_all_before_apply:
3576 comment('Remove all existing dynamic partitions and groups before '
3577 'applying full OTA')
3578 append('remove_all_groups')
3579
3580 for p, u in self._partition_updates.items():
3581 if u.src_group and not u.tgt_group:
3582 append('remove %s' % p)
3583
3584 for p, u in self._partition_updates.items():
3585 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3586 comment('Move partition %s from %s to default' % (p, u.src_group))
3587 append('move %s default' % p)
3588
3589 for p, u in self._partition_updates.items():
3590 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3591 comment('Shrink partition %s from %d to %d' %
3592 (p, u.src_size, u.tgt_size))
3593 append('resize %s %s' % (p, u.tgt_size))
3594
3595 for g, u in self._group_updates.items():
3596 if u.src_size is not None and u.tgt_size is None:
3597 append('remove_group %s' % g)
3598 if (u.src_size is not None and u.tgt_size is not None and
3599 u.src_size > u.tgt_size):
3600 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3601 append('resize_group %s %d' % (g, u.tgt_size))
3602
3603 for g, u in self._group_updates.items():
3604 if u.src_size is None and u.tgt_size is not None:
3605 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3606 append('add_group %s %d' % (g, u.tgt_size))
3607 if (u.src_size is not None and u.tgt_size is not None and
3608 u.src_size < u.tgt_size):
3609 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3610 append('resize_group %s %d' % (g, u.tgt_size))
3611
3612 for p, u in self._partition_updates.items():
3613 if u.tgt_group and not u.src_group:
3614 comment('Add partition %s to group %s' % (p, u.tgt_group))
3615 append('add %s %s' % (p, u.tgt_group))
3616
3617 for p, u in self._partition_updates.items():
3618 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003619 comment('Grow partition %s from %d to %d' %
3620 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003621 append('resize %s %d' % (p, u.tgt_size))
3622
3623 for p, u in self._partition_updates.items():
3624 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3625 comment('Move partition %s from default to %s' %
3626 (p, u.tgt_group))
3627 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003628
3629
Yifan Hong85ac5012021-01-07 14:43:46 -08003630def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003631 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003632 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003633
3634 Args:
3635 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3636
3637 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003638 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003639 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003640 tmp_dir = MakeTempDir('boot_', suffix='.img')
3641 try:
3642 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3643 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3644 if not os.path.isfile(ramdisk):
3645 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3646 return None
3647 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3648 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3649
3650 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3651 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3652 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3653 # the host environment.
3654 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3655 cwd=extracted_ramdisk)
3656
Yifan Hongc65a0542021-01-07 14:21:01 -08003657 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3658 prop_file = os.path.join(extracted_ramdisk, search_path)
3659 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003660 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003661 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3662
Yifan Hong7dc51172021-01-12 11:27:39 -08003663 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003664
Yifan Hong85ac5012021-01-07 14:43:46 -08003665 except ExternalError as e:
3666 logger.warning('Unable to get boot image build props: %s', e)
3667 return None
3668
3669
3670def GetBootImageTimestamp(boot_img):
3671 """
3672 Get timestamp from ramdisk within the boot image
3673
3674 Args:
3675 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3676
3677 Return:
3678 An integer that corresponds to the timestamp of the boot image, or None
3679 if file has unknown format. Raise exception if an unexpected error has
3680 occurred.
3681 """
3682 prop_file = GetBootImageBuildProp(boot_img)
3683 if not prop_file:
3684 return None
3685
3686 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3687 if props is None:
3688 return None
3689
3690 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003691 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3692 if timestamp:
3693 return int(timestamp)
3694 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3695 return None
3696
3697 except ExternalError as e:
3698 logger.warning('Unable to get boot image timestamp: %s', e)
3699 return None