blob: 9c3ca31648b2334fa4945b635b6a5fc51a974787 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Andrew Sculle077cf72021-02-18 10:27:29 +0000113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
114 'system', 'system_ext', 'vendor', 'vendor_boot',
115 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hong5057b952021-01-07 14:09:57 -0800131# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800132PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800133
Yifan Hongc65a0542021-01-07 14:21:01 -0800134# See sysprop.mk. If file is moved, add new search paths here; don't remove
135# existing search paths.
136RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700137
Tianjie Xu209db462016-05-24 17:34:52 -0700138class ErrorCode(object):
139 """Define error_codes for failures that happen during the actual
140 update package installation.
141
142 Error codes 0-999 are reserved for failures before the package
143 installation (i.e. low battery, package verification failure).
144 Detailed code in 'bootable/recovery/error_code.h' """
145
146 SYSTEM_VERIFICATION_FAILURE = 1000
147 SYSTEM_UPDATE_FAILURE = 1001
148 SYSTEM_UNEXPECTED_CONTENTS = 1002
149 SYSTEM_NONZERO_CONTENTS = 1003
150 SYSTEM_RECOVER_FAILURE = 1004
151 VENDOR_VERIFICATION_FAILURE = 2000
152 VENDOR_UPDATE_FAILURE = 2001
153 VENDOR_UNEXPECTED_CONTENTS = 2002
154 VENDOR_NONZERO_CONTENTS = 2003
155 VENDOR_RECOVER_FAILURE = 2004
156 OEM_PROP_MISMATCH = 3000
157 FINGERPRINT_MISMATCH = 3001
158 THUMBPRINT_MISMATCH = 3002
159 OLDER_BUILD = 3003
160 DEVICE_MISMATCH = 3004
161 BAD_PATCH_FILE = 3005
162 INSUFFICIENT_CACHE_SPACE = 3006
163 TUNE_PARTITION_FAILURE = 3007
164 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800165
Tao Bao80921982018-03-21 21:02:19 -0700166
Dan Albert8b72aef2015-03-23 19:13:21 -0700167class ExternalError(RuntimeError):
168 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700169
170
Tao Bao32fcdab2018-10-12 10:30:39 -0700171def InitLogging():
172 DEFAULT_LOGGING_CONFIG = {
173 'version': 1,
174 'disable_existing_loggers': False,
175 'formatters': {
176 'standard': {
177 'format':
178 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
179 'datefmt': '%Y-%m-%d %H:%M:%S',
180 },
181 },
182 'handlers': {
183 'default': {
184 'class': 'logging.StreamHandler',
185 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700186 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 },
188 },
189 'loggers': {
190 '': {
191 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700193 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 }
195 }
196 }
197 env_config = os.getenv('LOGGING_CONFIG')
198 if env_config:
199 with open(env_config) as f:
200 config = json.load(f)
201 else:
202 config = DEFAULT_LOGGING_CONFIG
203
204 # Increase the logging level for verbose mode.
205 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700206 config = copy.deepcopy(config)
207 config['handlers']['default']['level'] = 'INFO'
208
209 if OPTIONS.logfile:
210 config = copy.deepcopy(config)
211 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400212 'class': 'logging.FileHandler',
213 'formatter': 'standard',
214 'level': 'INFO',
215 'mode': 'w',
216 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700217 }
218 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700219
220 logging.config.dictConfig(config)
221
222
Yifan Hong8e332ff2020-07-29 17:51:55 -0700223def SetHostToolLocation(tool_name, location):
224 OPTIONS.host_tools[tool_name] = location
225
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900226def FindHostToolPath(tool_name):
227 """Finds the path to the host tool.
228
229 Args:
230 tool_name: name of the tool to find
231 Returns:
232 path to the tool if found under either one of the host_tools map or under
233 the same directory as this binary is located at. If not found, tool_name
234 is returned.
235 """
236 if tool_name in OPTIONS.host_tools:
237 return OPTIONS.host_tools[tool_name]
238
239 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 tool_path = os.path.join(my_dir, tool_name)
241 if os.path.exists(tool_path):
242 return tool_path
243
244 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700245
Tao Bao39451582017-05-04 11:10:47 -0700246def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700248
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 Args:
250 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700251 verbose: Whether the commands should be shown. Default to the global
252 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
254 stdin, etc. stdout and stderr will default to subprocess.PIPE and
255 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800256 universal_newlines will default to True, as most of the users in
257 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700258
259 Returns:
260 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700261 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700262 if 'stdout' not in kwargs and 'stderr' not in kwargs:
263 kwargs['stdout'] = subprocess.PIPE
264 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800265 if 'universal_newlines' not in kwargs:
266 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700267
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900268 if args:
269 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900271 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272
Tao Bao32fcdab2018-10-12 10:30:39 -0700273 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400274 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700276 return subprocess.Popen(args, **kwargs)
277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800403 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800411 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800459
460 # Boot image uses ro.[product.]bootimage instead of boot.
461 prop_partition = "bootimage" if partition == "boot" else partition
462
Daniel Normand5fe8622020-01-08 17:01:11 -0800463 # If provided a partition for this property, only look within that
464 # partition's build.prop.
465 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800466 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800467 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800468 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000469
470 prop_val = self._GetRawBuildProp(prop, partition)
471 if prop_val is not None:
472 return prop_val
473 raise ExternalError("couldn't find %s in %s.build.prop" %
474 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800475
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800477 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
479 return self._ResolveRoProductBuildProp(prop)
480
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 prop_val = self._GetRawBuildProp(prop, None)
482 if prop_val is not None:
483 return prop_val
484
485 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700486
487 def _ResolveRoProductBuildProp(self, prop):
488 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700490 if prop_val:
491 return prop_val
492
Steven Laver8e2086e2020-04-27 16:26:31 -0700493 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 source_order_val = self._GetRawBuildProp(
495 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700496 if source_order_val:
497 source_order = source_order_val.split(",")
498 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700499 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700500
501 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700503 raise ExternalError(
504 "Invalid ro.product.property_source_order '{}'".format(source_order))
505
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000506 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700507 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 "ro.product", "ro.product.{}".format(source_partition), 1)
509 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700510 if prop_val:
511 return prop_val
512
513 raise ExternalError("couldn't resolve {}".format(prop))
514
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 def _GetRoProductPropsDefaultSourceOrder(self):
516 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
517 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000518 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700519 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if android_version == "10":
522 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
523 # NOTE: float() conversion of android_version will have rounding error.
524 # We are checking for "9" or less, and using "< 10" is well outside of
525 # possible floating point rounding.
526 try:
527 android_version_val = float(android_version)
528 except ValueError:
529 android_version_val = 0
530 if android_version_val < 10:
531 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
532 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
533
Tianjieb37c5be2020-10-15 21:27:10 -0700534 def _GetPlatformVersion(self):
535 version_sdk = self.GetBuildProp("ro.build.version.sdk")
536 # init code switches to version_release_or_codename (see b/158483506). After
537 # API finalization, release_or_codename will be the same as release. This
538 # is the best effort to support pre-S dev stage builds.
539 if int(version_sdk) >= 30:
540 try:
541 return self.GetBuildProp("ro.build.version.release_or_codename")
542 except ExternalError:
543 logger.warning('Failed to find ro.build.version.release_or_codename')
544
545 return self.GetBuildProp("ro.build.version.release")
546
547 def _GetPartitionPlatformVersion(self, partition):
548 try:
549 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
550 partition)
551 except ExternalError:
552 return self.GetPartitionBuildProp("ro.build.version.release",
553 partition)
554
Tao Bao1c320f82019-10-04 23:25:12 -0700555 def GetOemProperty(self, key):
556 if self.oem_props is not None and key in self.oem_props:
557 return self.oem_dicts[0][key]
558 return self.GetBuildProp(key)
559
Daniel Normand5fe8622020-01-08 17:01:11 -0800560 def GetPartitionFingerprint(self, partition):
561 return self._partition_fingerprints.get(partition, None)
562
563 def CalculatePartitionFingerprint(self, partition):
564 try:
565 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
566 except ExternalError:
567 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
568 self.GetPartitionBuildProp("ro.product.brand", partition),
569 self.GetPartitionBuildProp("ro.product.name", partition),
570 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700571 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800572 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400573 self.GetPartitionBuildProp(
574 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800575 self.GetPartitionBuildProp("ro.build.type", partition),
576 self.GetPartitionBuildProp("ro.build.tags", partition))
577
Tao Bao1c320f82019-10-04 23:25:12 -0700578 def CalculateFingerprint(self):
579 if self.oem_props is None:
580 try:
581 return self.GetBuildProp("ro.build.fingerprint")
582 except ExternalError:
583 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
584 self.GetBuildProp("ro.product.brand"),
585 self.GetBuildProp("ro.product.name"),
586 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700587 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700588 self.GetBuildProp("ro.build.id"),
589 self.GetBuildProp("ro.build.version.incremental"),
590 self.GetBuildProp("ro.build.type"),
591 self.GetBuildProp("ro.build.tags"))
592 return "%s/%s/%s:%s" % (
593 self.GetOemProperty("ro.product.brand"),
594 self.GetOemProperty("ro.product.name"),
595 self.GetOemProperty("ro.product.device"),
596 self.GetBuildProp("ro.build.thumbprint"))
597
598 def WriteMountOemScript(self, script):
599 assert self.oem_props is not None
600 recovery_mount_options = self.info_dict.get("recovery_mount_options")
601 script.Mount("/oem", recovery_mount_options)
602
603 def WriteDeviceAssertions(self, script, oem_no_mount):
604 # Read the property directly if not using OEM properties.
605 if not self.oem_props:
606 script.AssertDevice(self.device)
607 return
608
609 # Otherwise assert OEM properties.
610 if not self.oem_dicts:
611 raise ExternalError(
612 "No OEM file provided to answer expected assertions")
613
614 for prop in self.oem_props.split():
615 values = []
616 for oem_dict in self.oem_dicts:
617 if prop in oem_dict:
618 values.append(oem_dict[prop])
619 if not values:
620 raise ExternalError(
621 "The OEM file is missing the property %s" % (prop,))
622 script.AssertOemProperty(prop, values, oem_no_mount)
623
624
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000625def ReadFromInputFile(input_file, fn):
626 """Reads the contents of fn from input zipfile or directory."""
627 if isinstance(input_file, zipfile.ZipFile):
628 return input_file.read(fn).decode()
629 else:
630 path = os.path.join(input_file, *fn.split("/"))
631 try:
632 with open(path) as f:
633 return f.read()
634 except IOError as e:
635 if e.errno == errno.ENOENT:
636 raise KeyError(fn)
637
638
Yifan Hong10482a22021-01-07 14:38:41 -0800639def ExtractFromInputFile(input_file, fn):
640 """Extracts the contents of fn from input zipfile or directory into a file."""
641 if isinstance(input_file, zipfile.ZipFile):
642 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500643 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800644 f.write(input_file.read(fn))
645 return tmp_file
646 else:
647 file = os.path.join(input_file, *fn.split("/"))
648 if not os.path.exists(file):
649 raise KeyError(fn)
650 return file
651
652
Tao Bao410ad8b2018-08-24 12:08:38 -0700653def LoadInfoDict(input_file, repacking=False):
654 """Loads the key/value pairs from the given input target_files.
655
Tianjiea85bdf02020-07-29 11:56:19 -0700656 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700657 checks and returns the parsed key/value pairs for to the given build. It's
658 usually called early when working on input target_files files, e.g. when
659 generating OTAs, or signing builds. Note that the function may be called
660 against an old target_files file (i.e. from past dessert releases). So the
661 property parsing needs to be backward compatible.
662
663 In a `META/misc_info.txt`, a few properties are stored as links to the files
664 in the PRODUCT_OUT directory. It works fine with the build system. However,
665 they are no longer available when (re)generating images from target_files zip.
666 When `repacking` is True, redirect these properties to the actual files in the
667 unzipped directory.
668
669 Args:
670 input_file: The input target_files file, which could be an open
671 zipfile.ZipFile instance, or a str for the dir that contains the files
672 unzipped from a target_files file.
673 repacking: Whether it's trying repack an target_files file after loading the
674 info dict (default: False). If so, it will rewrite a few loaded
675 properties (e.g. selinux_fc, root_dir) to point to the actual files in
676 target_files file. When doing repacking, `input_file` must be a dir.
677
678 Returns:
679 A dict that contains the parsed key/value pairs.
680
681 Raises:
682 AssertionError: On invalid input arguments.
683 ValueError: On malformed input values.
684 """
685 if repacking:
686 assert isinstance(input_file, str), \
687 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700688
Doug Zongkerc9253822014-02-04 12:17:58 -0800689 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000690 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800691
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700692 try:
Michael Runge6e836112014-04-15 17:40:21 -0700693 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700694 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700695 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700696
Tao Bao410ad8b2018-08-24 12:08:38 -0700697 if "recovery_api_version" not in d:
698 raise ValueError("Failed to find 'recovery_api_version'")
699 if "fstab_version" not in d:
700 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800701
Tao Bao410ad8b2018-08-24 12:08:38 -0700702 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700703 # "selinux_fc" properties should point to the file_contexts files
704 # (file_contexts.bin) under META/.
705 for key in d:
706 if key.endswith("selinux_fc"):
707 fc_basename = os.path.basename(d[key])
708 fc_config = os.path.join(input_file, "META", fc_basename)
709 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700710
Daniel Norman72c626f2019-05-13 15:58:14 -0700711 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700712
Tom Cherryd14b8952018-08-09 14:26:00 -0700713 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700714 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700715 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700716 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700717
David Anderson0ec64ac2019-12-06 12:21:18 -0800718 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700719 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700720 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800721 key_name = part_name + "_base_fs_file"
722 if key_name not in d:
723 continue
724 basename = os.path.basename(d[key_name])
725 base_fs_file = os.path.join(input_file, "META", basename)
726 if os.path.exists(base_fs_file):
727 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700728 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700729 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800730 "Failed to find %s base fs file: %s", part_name, base_fs_file)
731 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700732
Doug Zongker37974732010-09-16 17:44:38 -0700733 def makeint(key):
734 if key in d:
735 d[key] = int(d[key], 0)
736
737 makeint("recovery_api_version")
738 makeint("blocksize")
739 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700740 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700741 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700742 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700743 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800744 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700745
Steve Muckle903a1ca2020-05-07 17:32:10 -0700746 boot_images = "boot.img"
747 if "boot_images" in d:
748 boot_images = d["boot_images"]
749 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400750 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700751
Tao Bao765668f2019-10-04 22:03:00 -0700752 # Load recovery fstab if applicable.
753 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800754
Tianjie Xu861f4132018-09-12 11:49:33 -0700755 # Tries to load the build props for all partitions with care_map, including
756 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800757 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800758 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000759 d[partition_prop] = PartitionBuildProps.FromInputFile(
760 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700761 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800762
Tao Bao3ed35d32019-10-07 20:48:48 -0700763 # Set up the salt (based on fingerprint) that will be used when adding AVB
764 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800765 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700766 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800767 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800768 fingerprint = build_info.GetPartitionFingerprint(partition)
769 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400770 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400771 try:
772 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
773 except KeyError:
774 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700775 return d
776
Tao Baod1de6f32017-03-01 16:38:48 -0800777
Kelvin Zhang39aea442020-08-17 11:04:25 -0400778
Daniel Norman4cc9df62019-07-18 10:11:07 -0700779def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900780 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700781 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900782
Daniel Norman4cc9df62019-07-18 10:11:07 -0700783
784def LoadDictionaryFromFile(file_path):
785 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900786 return LoadDictionaryFromLines(lines)
787
788
Michael Runge6e836112014-04-15 17:40:21 -0700789def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700790 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700791 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700792 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700793 if not line or line.startswith("#"):
794 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700795 if "=" in line:
796 name, value = line.split("=", 1)
797 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700798 return d
799
Tao Baod1de6f32017-03-01 16:38:48 -0800800
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000801class PartitionBuildProps(object):
802 """The class holds the build prop of a particular partition.
803
804 This class loads the build.prop and holds the build properties for a given
805 partition. It also partially recognizes the 'import' statement in the
806 build.prop; and calculates alternative values of some specific build
807 properties during runtime.
808
809 Attributes:
810 input_file: a zipped target-file or an unzipped target-file directory.
811 partition: name of the partition.
812 props_allow_override: a list of build properties to search for the
813 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000814 build_props: a dict of build properties for the given partition.
815 prop_overrides: a set of props that are overridden by import.
816 placeholder_values: A dict of runtime variables' values to replace the
817 placeholders in the build.prop file. We expect exactly one value for
818 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000819 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400820
Tianjie Xu9afb2212020-05-10 21:48:15 +0000821 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000822 self.input_file = input_file
823 self.partition = name
824 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000825 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000826 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 self.prop_overrides = set()
828 self.placeholder_values = {}
829 if placeholder_values:
830 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000831
832 @staticmethod
833 def FromDictionary(name, build_props):
834 """Constructs an instance from a build prop dictionary."""
835
836 props = PartitionBuildProps("unknown", name)
837 props.build_props = build_props.copy()
838 return props
839
840 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000841 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800843
844 if name == "boot":
845 data = PartitionBuildProps._ReadBootPropFile(input_file)
846 else:
847 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
848
849 props = PartitionBuildProps(input_file, name, placeholder_values)
850 props._LoadBuildProp(data)
851 return props
852
853 @staticmethod
854 def _ReadBootPropFile(input_file):
855 """
856 Read build.prop for boot image from input_file.
857 Return empty string if not found.
858 """
859 try:
860 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
861 except KeyError:
862 logger.warning('Failed to read IMAGES/boot.img')
863 return ''
864 prop_file = GetBootImageBuildProp(boot_img)
865 if prop_file is None:
866 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500867 with open(prop_file, "r") as f:
868 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800869
870 @staticmethod
871 def _ReadPartitionPropFile(input_file, name):
872 """
873 Read build.prop for name from input_file.
874 Return empty string if not found.
875 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000876 data = ''
877 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
878 '{}/build.prop'.format(name.upper())]:
879 try:
880 data = ReadFromInputFile(input_file, prop_file)
881 break
882 except KeyError:
883 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800884 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000885
Yifan Hong125d0b62020-09-24 17:07:03 -0700886 @staticmethod
887 def FromBuildPropFile(name, build_prop_file):
888 """Constructs an instance from a build prop file."""
889
890 props = PartitionBuildProps("unknown", name)
891 with open(build_prop_file) as f:
892 props._LoadBuildProp(f.read())
893 return props
894
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 def _LoadBuildProp(self, data):
896 for line in data.split('\n'):
897 line = line.strip()
898 if not line or line.startswith("#"):
899 continue
900 if line.startswith("import"):
901 overrides = self._ImportParser(line)
902 duplicates = self.prop_overrides.intersection(overrides.keys())
903 if duplicates:
904 raise ValueError('prop {} is overridden multiple times'.format(
905 ','.join(duplicates)))
906 self.prop_overrides = self.prop_overrides.union(overrides.keys())
907 self.build_props.update(overrides)
908 elif "=" in line:
909 name, value = line.split("=", 1)
910 if name in self.prop_overrides:
911 raise ValueError('prop {} is set again after overridden by import '
912 'statement'.format(name))
913 self.build_props[name] = value
914
915 def _ImportParser(self, line):
916 """Parses the build prop in a given import statement."""
917
918 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400919 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000920 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700921
922 if len(tokens) == 3:
923 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
924 return {}
925
Tianjie Xu9afb2212020-05-10 21:48:15 +0000926 import_path = tokens[1]
927 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
928 raise ValueError('Unrecognized import path {}'.format(line))
929
930 # We only recognize a subset of import statement that the init process
931 # supports. And we can loose the restriction based on how the dynamic
932 # fingerprint is used in practice. The placeholder format should be
933 # ${placeholder}, and its value should be provided by the caller through
934 # the placeholder_values.
935 for prop, value in self.placeholder_values.items():
936 prop_place_holder = '${{{}}}'.format(prop)
937 if prop_place_holder in import_path:
938 import_path = import_path.replace(prop_place_holder, value)
939 if '$' in import_path:
940 logger.info('Unresolved place holder in import path %s', import_path)
941 return {}
942
943 import_path = import_path.replace('/{}'.format(self.partition),
944 self.partition.upper())
945 logger.info('Parsing build props override from %s', import_path)
946
947 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
948 d = LoadDictionaryFromLines(lines)
949 return {key: val for key, val in d.items()
950 if key in self.props_allow_override}
951
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000952 def GetProp(self, prop):
953 return self.build_props.get(prop)
954
955
Tianjie Xucfa86222016-03-07 16:31:19 -0800956def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
957 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700958 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700959 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700960 self.mount_point = mount_point
961 self.fs_type = fs_type
962 self.device = device
963 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700964 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700965 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700966
967 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800968 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700969 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700970 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700971 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700972
Tao Baod1de6f32017-03-01 16:38:48 -0800973 assert fstab_version == 2
974
975 d = {}
976 for line in data.split("\n"):
977 line = line.strip()
978 if not line or line.startswith("#"):
979 continue
980
981 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
982 pieces = line.split()
983 if len(pieces) != 5:
984 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
985
986 # Ignore entries that are managed by vold.
987 options = pieces[4]
988 if "voldmanaged=" in options:
989 continue
990
991 # It's a good line, parse it.
992 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700993 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800994 options = options.split(",")
995 for i in options:
996 if i.startswith("length="):
997 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700998 elif i == "slotselect":
999 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001000 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001001 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001003
Tao Baod1de6f32017-03-01 16:38:48 -08001004 mount_flags = pieces[3]
1005 # Honor the SELinux context if present.
1006 context = None
1007 for i in mount_flags.split(","):
1008 if i.startswith("context="):
1009 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001010
Tao Baod1de6f32017-03-01 16:38:48 -08001011 mount_point = pieces[1]
1012 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001013 device=pieces[0], length=length, context=context,
1014 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001015
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001016 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001017 # system. Other areas assume system is always at "/system" so point /system
1018 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001019 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001020 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001021 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001022 return d
1023
1024
Tao Bao765668f2019-10-04 22:03:00 -07001025def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1026 """Finds the path to recovery fstab and loads its contents."""
1027 # recovery fstab is only meaningful when installing an update via recovery
1028 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001029 if info_dict.get('ab_update') == 'true' and \
1030 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001031 return None
1032
1033 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1034 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1035 # cases, since it may load the info_dict from an old build (e.g. when
1036 # generating incremental OTAs from that build).
1037 system_root_image = info_dict.get('system_root_image') == 'true'
1038 if info_dict.get('no_recovery') != 'true':
1039 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1040 if isinstance(input_file, zipfile.ZipFile):
1041 if recovery_fstab_path not in input_file.namelist():
1042 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1043 else:
1044 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1045 if not os.path.exists(path):
1046 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1047 return LoadRecoveryFSTab(
1048 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1049 system_root_image)
1050
1051 if info_dict.get('recovery_as_boot') == 'true':
1052 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1053 if isinstance(input_file, zipfile.ZipFile):
1054 if recovery_fstab_path not in input_file.namelist():
1055 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1056 else:
1057 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1058 if not os.path.exists(path):
1059 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1060 return LoadRecoveryFSTab(
1061 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1062 system_root_image)
1063
1064 return None
1065
1066
Doug Zongker37974732010-09-16 17:44:38 -07001067def DumpInfoDict(d):
1068 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001069 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001070
Dan Albert8b72aef2015-03-23 19:13:21 -07001071
Daniel Norman55417142019-11-25 16:04:36 -08001072def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001073 """Merges dynamic partition info variables.
1074
1075 Args:
1076 framework_dict: The dictionary of dynamic partition info variables from the
1077 partial framework target files.
1078 vendor_dict: The dictionary of dynamic partition info variables from the
1079 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001080
1081 Returns:
1082 The merged dynamic partition info dictionary.
1083 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001084
1085 def uniq_concat(a, b):
1086 combined = set(a.split(" "))
1087 combined.update(set(b.split(" ")))
1088 combined = [item.strip() for item in combined if item.strip()]
1089 return " ".join(sorted(combined))
1090
1091 if (framework_dict.get("use_dynamic_partitions") !=
1092 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1093 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1094
1095 merged_dict = {"use_dynamic_partitions": "true"}
1096
1097 merged_dict["dynamic_partition_list"] = uniq_concat(
1098 framework_dict.get("dynamic_partition_list", ""),
1099 vendor_dict.get("dynamic_partition_list", ""))
1100
1101 # Super block devices are defined by the vendor dict.
1102 if "super_block_devices" in vendor_dict:
1103 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1104 for block_device in merged_dict["super_block_devices"].split(" "):
1105 key = "super_%s_device_size" % block_device
1106 if key not in vendor_dict:
1107 raise ValueError("Vendor dict does not contain required key %s." % key)
1108 merged_dict[key] = vendor_dict[key]
1109
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001110 # Partition groups and group sizes are defined by the vendor dict because
1111 # these values may vary for each board that uses a shared system image.
1112 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001113 for partition_group in merged_dict["super_partition_groups"].split(" "):
1114 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001115 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001116 if key not in vendor_dict:
1117 raise ValueError("Vendor dict does not contain required key %s." % key)
1118 merged_dict[key] = vendor_dict[key]
1119
1120 # Set the partition group's partition list using a concatenation of the
1121 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001122 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001123 merged_dict[key] = uniq_concat(
1124 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301125
Daniel Normanb0c75912020-09-24 14:30:21 -07001126 # Various other flags should be copied from the vendor dict, if defined.
1127 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1128 "super_metadata_device", "super_partition_error_limit",
1129 "super_partition_size"):
1130 if key in vendor_dict.keys():
1131 merged_dict[key] = vendor_dict[key]
1132
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001133 return merged_dict
1134
1135
Daniel Norman21c34f72020-11-11 17:25:50 -08001136def PartitionMapFromTargetFiles(target_files_dir):
1137 """Builds a map from partition -> path within an extracted target files directory."""
1138 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1139 possible_subdirs = {
1140 "system": ["SYSTEM"],
1141 "vendor": ["VENDOR", "SYSTEM/vendor"],
1142 "product": ["PRODUCT", "SYSTEM/product"],
1143 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1144 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1145 "vendor_dlkm": [
1146 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1147 ],
1148 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1149 }
1150 partition_map = {}
1151 for partition, subdirs in possible_subdirs.items():
1152 for subdir in subdirs:
1153 if os.path.exists(os.path.join(target_files_dir, subdir)):
1154 partition_map[partition] = subdir
1155 break
1156 return partition_map
1157
1158
Daniel Normand3351562020-10-29 12:33:11 -07001159def SharedUidPartitionViolations(uid_dict, partition_groups):
1160 """Checks for APK sharedUserIds that cross partition group boundaries.
1161
1162 This uses a single or merged build's shareduid_violation_modules.json
1163 output file, as generated by find_shareduid_violation.py or
1164 core/tasks/find-shareduid-violation.mk.
1165
1166 An error is defined as a sharedUserId that is found in a set of partitions
1167 that span more than one partition group.
1168
1169 Args:
1170 uid_dict: A dictionary created by using the standard json module to read a
1171 complete shareduid_violation_modules.json file.
1172 partition_groups: A list of groups, where each group is a list of
1173 partitions.
1174
1175 Returns:
1176 A list of error messages.
1177 """
1178 errors = []
1179 for uid, partitions in uid_dict.items():
1180 found_in_groups = [
1181 group for group in partition_groups
1182 if set(partitions.keys()) & set(group)
1183 ]
1184 if len(found_in_groups) > 1:
1185 errors.append(
1186 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1187 % (uid, ",".join(sorted(partitions.keys()))))
1188 return errors
1189
1190
Daniel Norman21c34f72020-11-11 17:25:50 -08001191def RunHostInitVerifier(product_out, partition_map):
1192 """Runs host_init_verifier on the init rc files within partitions.
1193
1194 host_init_verifier searches the etc/init path within each partition.
1195
1196 Args:
1197 product_out: PRODUCT_OUT directory, containing partition directories.
1198 partition_map: A map of partition name -> relative path within product_out.
1199 """
1200 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1201 cmd = ["host_init_verifier"]
1202 for partition, path in partition_map.items():
1203 if partition not in allowed_partitions:
1204 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1205 partition)
1206 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1207 # Add --property-contexts if the file exists on the partition.
1208 property_contexts = "%s_property_contexts" % (
1209 "plat" if partition == "system" else partition)
1210 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1211 property_contexts)
1212 if os.path.exists(property_contexts_path):
1213 cmd.append("--property-contexts=%s" % property_contexts_path)
1214 # Add the passwd file if the file exists on the partition.
1215 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1216 if os.path.exists(passwd_path):
1217 cmd.extend(["-p", passwd_path])
1218 return RunAndCheckOutput(cmd)
1219
1220
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001221def AppendAVBSigningArgs(cmd, partition):
1222 """Append signing arguments for avbtool."""
1223 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1224 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001225 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1226 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1227 if os.path.exists(new_key_path):
1228 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001229 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1230 if key_path and algorithm:
1231 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001232 avb_salt = OPTIONS.info_dict.get("avb_salt")
1233 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001234 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001235 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001236
1237
Tao Bao765668f2019-10-04 22:03:00 -07001238def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001239 """Returns the VBMeta arguments for partition.
1240
1241 It sets up the VBMeta argument by including the partition descriptor from the
1242 given 'image', or by configuring the partition as a chained partition.
1243
1244 Args:
1245 partition: The name of the partition (e.g. "system").
1246 image: The path to the partition image.
1247 info_dict: A dict returned by common.LoadInfoDict(). Will use
1248 OPTIONS.info_dict if None has been given.
1249
1250 Returns:
1251 A list of VBMeta arguments.
1252 """
1253 if info_dict is None:
1254 info_dict = OPTIONS.info_dict
1255
1256 # Check if chain partition is used.
1257 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001258 if not key_path:
1259 return ["--include_descriptors_from_image", image]
1260
1261 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1262 # into vbmeta.img. The recovery image will be configured on an independent
1263 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1264 # See details at
1265 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001266 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001267 return []
1268
1269 # Otherwise chain the partition into vbmeta.
1270 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1271 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001272
1273
Tao Bao02a08592018-07-22 12:40:45 -07001274def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1275 """Constructs and returns the arg to build or verify a chained partition.
1276
1277 Args:
1278 partition: The partition name.
1279 info_dict: The info dict to look up the key info and rollback index
1280 location.
1281 key: The key to be used for building or verifying the partition. Defaults to
1282 the key listed in info_dict.
1283
1284 Returns:
1285 A string of form "partition:rollback_index_location:key" that can be used to
1286 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001287 """
1288 if key is None:
1289 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001290 if key and not os.path.exists(key) and OPTIONS.search_path:
1291 new_key_path = os.path.join(OPTIONS.search_path, key)
1292 if os.path.exists(new_key_path):
1293 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001294 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001295 rollback_index_location = info_dict[
1296 "avb_" + partition + "_rollback_index_location"]
1297 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1298
1299
Tianjie20dd8f22020-04-19 15:51:16 -07001300def ConstructAftlMakeImageCommands(output_image):
1301 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001302
1303 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001304 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001305 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1306 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1307 'No AFTL manufacturer key provided.'
1308
1309 vbmeta_image = MakeTempFile()
1310 os.rename(output_image, vbmeta_image)
1311 build_info = BuildInfo(OPTIONS.info_dict)
1312 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001313 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001314 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001315 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001316 "--vbmeta_image_path", vbmeta_image,
1317 "--output", output_image,
1318 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001319 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001320 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1321 "--algorithm", "SHA256_RSA4096",
1322 "--padding", "4096"]
1323 if OPTIONS.aftl_signer_helper:
1324 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001325 return aftl_cmd
1326
1327
1328def AddAftlInclusionProof(output_image):
1329 """Appends the aftl inclusion proof to the vbmeta image."""
1330
1331 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 RunAndCheckOutput(aftl_cmd)
1333
1334 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1335 output_image, '--transparency_log_pub_keys',
1336 OPTIONS.aftl_key_path]
1337 RunAndCheckOutput(verify_cmd)
1338
1339
Daniel Norman276f0622019-07-26 14:13:51 -07001340def BuildVBMeta(image_path, partitions, name, needed_partitions):
1341 """Creates a VBMeta image.
1342
1343 It generates the requested VBMeta image. The requested image could be for
1344 top-level or chained VBMeta image, which is determined based on the name.
1345
1346 Args:
1347 image_path: The output path for the new VBMeta image.
1348 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001349 values. Only valid partition names are accepted, as partitions listed
1350 in common.AVB_PARTITIONS and custom partitions listed in
1351 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001352 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1353 needed_partitions: Partitions whose descriptors should be included into the
1354 generated VBMeta image.
1355
1356 Raises:
1357 AssertionError: On invalid input args.
1358 """
1359 avbtool = OPTIONS.info_dict["avb_avbtool"]
1360 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1361 AppendAVBSigningArgs(cmd, name)
1362
Hongguang Chenf23364d2020-04-27 18:36:36 -07001363 custom_partitions = OPTIONS.info_dict.get(
1364 "avb_custom_images_partition_list", "").strip().split()
1365
Daniel Norman276f0622019-07-26 14:13:51 -07001366 for partition, path in partitions.items():
1367 if partition not in needed_partitions:
1368 continue
1369 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001370 partition in AVB_VBMETA_PARTITIONS or
1371 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001372 'Unknown partition: {}'.format(partition)
1373 assert os.path.exists(path), \
1374 'Failed to find {} for {}'.format(path, partition)
1375 cmd.extend(GetAvbPartitionArg(partition, path))
1376
1377 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1378 if args and args.strip():
1379 split_args = shlex.split(args)
1380 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001381 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001382 # as a path relative to source tree, which may not be available at the
1383 # same location when running this script (we have the input target_files
1384 # zip only). For such cases, we additionally scan other locations (e.g.
1385 # IMAGES/, RADIO/, etc) before bailing out.
1386 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001387 chained_image = split_args[index + 1]
1388 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001389 continue
1390 found = False
1391 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1392 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001393 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001394 if os.path.exists(alt_path):
1395 split_args[index + 1] = alt_path
1396 found = True
1397 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001398 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001399 cmd.extend(split_args)
1400
1401 RunAndCheckOutput(cmd)
1402
Tianjie Xueaed60c2020-03-12 00:33:28 -07001403 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001404 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001405 AddAftlInclusionProof(image_path)
1406
Daniel Norman276f0622019-07-26 14:13:51 -07001407
J. Avila98cd4cc2020-06-10 20:09:10 +00001408def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001409 ramdisk_img = tempfile.NamedTemporaryFile()
1410
1411 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1412 cmd = ["mkbootfs", "-f", fs_config_file,
1413 os.path.join(sourcedir, "RAMDISK")]
1414 else:
1415 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1416 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001417 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001418 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001419 stdout=ramdisk_img.file.fileno())
1420 else:
1421 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001422
1423 p2.wait()
1424 p1.wait()
1425 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001426 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001427
1428 return ramdisk_img
1429
1430
Steve Muckle9793cf62020-04-08 18:27:00 -07001431def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001432 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001433 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001434
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001435 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001436 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1437 we are building a two-step special image (i.e. building a recovery image to
1438 be loaded into /boot in two-step OTAs).
1439
1440 Return the image data, or None if sourcedir does not appear to contains files
1441 for building the requested image.
1442 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001443
Yifan Hong63c5ca12020-10-08 11:54:02 -07001444 if info_dict is None:
1445 info_dict = OPTIONS.info_dict
1446
Steve Muckle9793cf62020-04-08 18:27:00 -07001447 # "boot" or "recovery", without extension.
1448 partition_name = os.path.basename(sourcedir).lower()
1449
Yifan Hong63c5ca12020-10-08 11:54:02 -07001450 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001451 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001452 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1453 logger.info("Excluded kernel binary from recovery image.")
1454 else:
1455 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001456 else:
1457 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001458 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001459 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001460 return None
1461
1462 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001463 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001464
Doug Zongkereef39442009-04-02 12:14:19 -07001465 img = tempfile.NamedTemporaryFile()
1466
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001467 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001468 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1469 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001470
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001471 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1472 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1473
Yifan Hong63c5ca12020-10-08 11:54:02 -07001474 cmd = [mkbootimg]
1475 if kernel:
1476 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001477
Benoit Fradina45a8682014-07-14 21:00:43 +02001478 fn = os.path.join(sourcedir, "second")
1479 if os.access(fn, os.F_OK):
1480 cmd.append("--second")
1481 cmd.append(fn)
1482
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001483 fn = os.path.join(sourcedir, "dtb")
1484 if os.access(fn, os.F_OK):
1485 cmd.append("--dtb")
1486 cmd.append(fn)
1487
Doug Zongker171f1cd2009-06-15 22:36:37 -07001488 fn = os.path.join(sourcedir, "cmdline")
1489 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001490 cmd.append("--cmdline")
1491 cmd.append(open(fn).read().rstrip("\n"))
1492
1493 fn = os.path.join(sourcedir, "base")
1494 if os.access(fn, os.F_OK):
1495 cmd.append("--base")
1496 cmd.append(open(fn).read().rstrip("\n"))
1497
Ying Wang4de6b5b2010-08-25 14:29:34 -07001498 fn = os.path.join(sourcedir, "pagesize")
1499 if os.access(fn, os.F_OK):
1500 cmd.append("--pagesize")
1501 cmd.append(open(fn).read().rstrip("\n"))
1502
Steve Mucklef84668e2020-03-16 19:13:46 -07001503 if partition_name == "recovery":
1504 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301505 if not args:
1506 # Fall back to "mkbootimg_args" for recovery image
1507 # in case "recovery_mkbootimg_args" is not set.
1508 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001509 else:
1510 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001511 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001512 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001513
Tao Bao76def242017-11-21 09:25:31 -08001514 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001515 if args and args.strip():
1516 cmd.extend(shlex.split(args))
1517
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001518 if has_ramdisk:
1519 cmd.extend(["--ramdisk", ramdisk_img.name])
1520
Tao Baod95e9fd2015-03-29 23:07:41 -07001521 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001522 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001523 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001524 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001526 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001527
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001528 if partition_name == "recovery":
1529 if info_dict.get("include_recovery_dtbo") == "true":
1530 fn = os.path.join(sourcedir, "recovery_dtbo")
1531 cmd.extend(["--recovery_dtbo", fn])
1532 if info_dict.get("include_recovery_acpio") == "true":
1533 fn = os.path.join(sourcedir, "recovery_acpio")
1534 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001535
Tao Bao986ee862018-10-04 15:46:16 -07001536 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001537
Tao Bao76def242017-11-21 09:25:31 -08001538 if (info_dict.get("boot_signer") == "true" and
1539 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001540 # Hard-code the path as "/boot" for two-step special recovery image (which
1541 # will be loaded into /boot during the two-step OTA).
1542 if two_step_image:
1543 path = "/boot"
1544 else:
Tao Baobf70c312017-07-11 17:27:55 -07001545 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001546 cmd = [OPTIONS.boot_signer_path]
1547 cmd.extend(OPTIONS.boot_signer_args)
1548 cmd.extend([path, img.name,
1549 info_dict["verity_key"] + ".pk8",
1550 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001551 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001552
Tao Baod95e9fd2015-03-29 23:07:41 -07001553 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001554 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001555 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001556 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001557 # We have switched from the prebuilt futility binary to using the tool
1558 # (futility-host) built from the source. Override the setting in the old
1559 # TF.zip.
1560 futility = info_dict["futility"]
1561 if futility.startswith("prebuilts/"):
1562 futility = "futility-host"
1563 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001564 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001565 info_dict["vboot_key"] + ".vbprivk",
1566 info_dict["vboot_subkey"] + ".vbprivk",
1567 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001568 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001569 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001570
Tao Baof3282b42015-04-01 11:21:55 -07001571 # Clean up the temp files.
1572 img_unsigned.close()
1573 img_keyblock.close()
1574
David Zeuthen8fecb282017-12-01 16:24:01 -05001575 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001576 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001577 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001578 if partition_name == "recovery":
1579 part_size = info_dict["recovery_size"]
1580 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001581 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001582 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001583 "--partition_size", str(part_size), "--partition_name",
1584 partition_name]
1585 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001586 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001587 if args and args.strip():
1588 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001589 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001590
1591 img.seek(os.SEEK_SET, 0)
1592 data = img.read()
1593
1594 if has_ramdisk:
1595 ramdisk_img.close()
1596 img.close()
1597
1598 return data
1599
1600
Doug Zongkerd5131602012-08-02 14:46:42 -07001601def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001602 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001603 """Return a File object with the desired bootable image.
1604
1605 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1606 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1607 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001608
Doug Zongker55d93282011-01-25 17:03:34 -08001609 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1610 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001611 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001612 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001613
1614 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1615 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001616 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001617 return File.FromLocalFile(name, prebuilt_path)
1618
Tao Bao32fcdab2018-10-12 10:30:39 -07001619 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001620
1621 if info_dict is None:
1622 info_dict = OPTIONS.info_dict
1623
1624 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001625 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1626 # for recovery.
1627 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1628 prebuilt_name != "boot.img" or
1629 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001630
Doug Zongker6f1d0312014-08-22 08:07:12 -07001631 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001632 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001633 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001634 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001635 if data:
1636 return File(name, data)
1637 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001638
Doug Zongkereef39442009-04-02 12:14:19 -07001639
Steve Mucklee1b10862019-07-10 10:49:37 -07001640def _BuildVendorBootImage(sourcedir, info_dict=None):
1641 """Build a vendor boot image from the specified sourcedir.
1642
1643 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1644 turn them into a vendor boot image.
1645
1646 Return the image data, or None if sourcedir does not appear to contains files
1647 for building the requested image.
1648 """
1649
1650 if info_dict is None:
1651 info_dict = OPTIONS.info_dict
1652
1653 img = tempfile.NamedTemporaryFile()
1654
J. Avila98cd4cc2020-06-10 20:09:10 +00001655 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1656 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001657
1658 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1659 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1660
1661 cmd = [mkbootimg]
1662
1663 fn = os.path.join(sourcedir, "dtb")
1664 if os.access(fn, os.F_OK):
1665 cmd.append("--dtb")
1666 cmd.append(fn)
1667
1668 fn = os.path.join(sourcedir, "vendor_cmdline")
1669 if os.access(fn, os.F_OK):
1670 cmd.append("--vendor_cmdline")
1671 cmd.append(open(fn).read().rstrip("\n"))
1672
1673 fn = os.path.join(sourcedir, "base")
1674 if os.access(fn, os.F_OK):
1675 cmd.append("--base")
1676 cmd.append(open(fn).read().rstrip("\n"))
1677
1678 fn = os.path.join(sourcedir, "pagesize")
1679 if os.access(fn, os.F_OK):
1680 cmd.append("--pagesize")
1681 cmd.append(open(fn).read().rstrip("\n"))
1682
1683 args = info_dict.get("mkbootimg_args")
1684 if args and args.strip():
1685 cmd.extend(shlex.split(args))
1686
1687 args = info_dict.get("mkbootimg_version_args")
1688 if args and args.strip():
1689 cmd.extend(shlex.split(args))
1690
1691 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1692 cmd.extend(["--vendor_boot", img.name])
1693
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001694 ramdisk_fragment_imgs = []
1695 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1696 if os.access(fn, os.F_OK):
1697 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1698 for ramdisk_fragment in ramdisk_fragments:
1699 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1700 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1701 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1702 # Use prebuilt image if found, else create ramdisk from supplied files.
1703 if os.access(fn, os.F_OK):
1704 ramdisk_fragment_pathname = fn
1705 else:
1706 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1707 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1708 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1709 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1710 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1711
Steve Mucklee1b10862019-07-10 10:49:37 -07001712 RunAndCheckOutput(cmd)
1713
1714 # AVB: if enabled, calculate and add hash.
1715 if info_dict.get("avb_enable") == "true":
1716 avbtool = info_dict["avb_avbtool"]
1717 part_size = info_dict["vendor_boot_size"]
1718 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001719 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001720 AppendAVBSigningArgs(cmd, "vendor_boot")
1721 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1722 if args and args.strip():
1723 cmd.extend(shlex.split(args))
1724 RunAndCheckOutput(cmd)
1725
1726 img.seek(os.SEEK_SET, 0)
1727 data = img.read()
1728
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001729 for f in ramdisk_fragment_imgs:
1730 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001731 ramdisk_img.close()
1732 img.close()
1733
1734 return data
1735
1736
1737def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1738 info_dict=None):
1739 """Return a File object with the desired vendor boot image.
1740
1741 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1742 the source files in 'unpack_dir'/'tree_subdir'."""
1743
1744 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1745 if os.path.exists(prebuilt_path):
1746 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1747 return File.FromLocalFile(name, prebuilt_path)
1748
1749 logger.info("building image from target_files %s...", tree_subdir)
1750
1751 if info_dict is None:
1752 info_dict = OPTIONS.info_dict
1753
Kelvin Zhang0876c412020-06-23 15:06:58 -04001754 data = _BuildVendorBootImage(
1755 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001756 if data:
1757 return File(name, data)
1758 return None
1759
1760
Narayan Kamatha07bf042017-08-14 14:49:21 +01001761def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001762 """Gunzips the given gzip compressed file to a given output file."""
1763 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001764 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001765 shutil.copyfileobj(in_file, out_file)
1766
1767
Tao Bao0ff15de2019-03-20 11:26:06 -07001768def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001769 """Unzips the archive to the given directory.
1770
1771 Args:
1772 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001773 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001774 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1775 archvie. Non-matching patterns will be filtered out. If there's no match
1776 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001777 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001778 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001779 if patterns is not None:
1780 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001781 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001782 names = input_zip.namelist()
1783 filtered = [
1784 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1785
1786 # There isn't any matching files. Don't unzip anything.
1787 if not filtered:
1788 return
1789 cmd.extend(filtered)
1790
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001791 RunAndCheckOutput(cmd)
1792
1793
Doug Zongker75f17362009-12-08 13:46:44 -08001794def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001795 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001796
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001797 Args:
1798 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1799 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1800
1801 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1802 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001803
Tao Bao1c830bf2017-12-25 10:43:47 -08001804 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001805 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001806 """
Doug Zongkereef39442009-04-02 12:14:19 -07001807
Tao Bao1c830bf2017-12-25 10:43:47 -08001808 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001809 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1810 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001811 UnzipToDir(m.group(1), tmp, pattern)
1812 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001813 filename = m.group(1)
1814 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001815 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001816
Tao Baodba59ee2018-01-09 13:21:02 -08001817 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001818
1819
Yifan Hong8a66a712019-04-04 15:37:57 -07001820def GetUserImage(which, tmpdir, input_zip,
1821 info_dict=None,
1822 allow_shared_blocks=None,
1823 hashtree_info_generator=None,
1824 reset_file_map=False):
1825 """Returns an Image object suitable for passing to BlockImageDiff.
1826
1827 This function loads the specified image from the given path. If the specified
1828 image is sparse, it also performs additional processing for OTA purpose. For
1829 example, it always adds block 0 to clobbered blocks list. It also detects
1830 files that cannot be reconstructed from the block list, for whom we should
1831 avoid applying imgdiff.
1832
1833 Args:
1834 which: The partition name.
1835 tmpdir: The directory that contains the prebuilt image and block map file.
1836 input_zip: The target-files ZIP archive.
1837 info_dict: The dict to be looked up for relevant info.
1838 allow_shared_blocks: If image is sparse, whether having shared blocks is
1839 allowed. If none, it is looked up from info_dict.
1840 hashtree_info_generator: If present and image is sparse, generates the
1841 hashtree_info for this sparse image.
1842 reset_file_map: If true and image is sparse, reset file map before returning
1843 the image.
1844 Returns:
1845 A Image object. If it is a sparse image and reset_file_map is False, the
1846 image will have file_map info loaded.
1847 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001848 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001849 info_dict = LoadInfoDict(input_zip)
1850
1851 is_sparse = info_dict.get("extfs_sparse_flag")
1852
1853 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1854 # shared blocks (i.e. some blocks will show up in multiple files' block
1855 # list). We can only allocate such shared blocks to the first "owner", and
1856 # disable imgdiff for all later occurrences.
1857 if allow_shared_blocks is None:
1858 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1859
1860 if is_sparse:
1861 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1862 hashtree_info_generator)
1863 if reset_file_map:
1864 img.ResetFileMap()
1865 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001866 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001867
1868
1869def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1870 """Returns a Image object suitable for passing to BlockImageDiff.
1871
1872 This function loads the specified non-sparse image from the given path.
1873
1874 Args:
1875 which: The partition name.
1876 tmpdir: The directory that contains the prebuilt image and block map file.
1877 Returns:
1878 A Image object.
1879 """
1880 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1881 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1882
1883 # The image and map files must have been created prior to calling
1884 # ota_from_target_files.py (since LMP).
1885 assert os.path.exists(path) and os.path.exists(mappath)
1886
Tianjie Xu41976c72019-07-03 13:57:01 -07001887 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1888
Yifan Hong8a66a712019-04-04 15:37:57 -07001889
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001890def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1891 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001892 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1893
1894 This function loads the specified sparse image from the given path, and
1895 performs additional processing for OTA purpose. For example, it always adds
1896 block 0 to clobbered blocks list. It also detects files that cannot be
1897 reconstructed from the block list, for whom we should avoid applying imgdiff.
1898
1899 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001900 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001901 tmpdir: The directory that contains the prebuilt image and block map file.
1902 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001903 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001904 hashtree_info_generator: If present, generates the hashtree_info for this
1905 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001906 Returns:
1907 A SparseImage object, with file_map info loaded.
1908 """
Tao Baoc765cca2018-01-31 17:32:40 -08001909 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1910 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1911
1912 # The image and map files must have been created prior to calling
1913 # ota_from_target_files.py (since LMP).
1914 assert os.path.exists(path) and os.path.exists(mappath)
1915
1916 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1917 # it to clobbered_blocks so that it will be written to the target
1918 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1919 clobbered_blocks = "0"
1920
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001921 image = sparse_img.SparseImage(
1922 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1923 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001924
1925 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1926 # if they contain all zeros. We can't reconstruct such a file from its block
1927 # list. Tag such entries accordingly. (Bug: 65213616)
1928 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001929 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001930 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001931 continue
1932
Tom Cherryd14b8952018-08-09 14:26:00 -07001933 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1934 # filename listed in system.map may contain an additional leading slash
1935 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1936 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001937 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001938
Tom Cherryd14b8952018-08-09 14:26:00 -07001939 # Special handling another case, where files not under /system
1940 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001941 if which == 'system' and not arcname.startswith('SYSTEM'):
1942 arcname = 'ROOT/' + arcname
1943
1944 assert arcname in input_zip.namelist(), \
1945 "Failed to find the ZIP entry for {}".format(entry)
1946
Tao Baoc765cca2018-01-31 17:32:40 -08001947 info = input_zip.getinfo(arcname)
1948 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001949
1950 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001951 # image, check the original block list to determine its completeness. Note
1952 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001953 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001954 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001955
Tao Baoc765cca2018-01-31 17:32:40 -08001956 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1957 ranges.extra['incomplete'] = True
1958
1959 return image
1960
1961
Doug Zongkereef39442009-04-02 12:14:19 -07001962def GetKeyPasswords(keylist):
1963 """Given a list of keys, prompt the user to enter passwords for
1964 those which require them. Return a {key: password} dict. password
1965 will be None if the key has no password."""
1966
Doug Zongker8ce7c252009-05-22 13:34:54 -07001967 no_passwords = []
1968 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001969 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001970 devnull = open("/dev/null", "w+b")
1971 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001972 # We don't need a password for things that aren't really keys.
1973 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001974 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001975 continue
1976
T.R. Fullhart37e10522013-03-18 10:31:26 -07001977 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001978 "-inform", "DER", "-nocrypt"],
1979 stdin=devnull.fileno(),
1980 stdout=devnull.fileno(),
1981 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001982 p.communicate()
1983 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001984 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001985 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001986 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001987 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1988 "-inform", "DER", "-passin", "pass:"],
1989 stdin=devnull.fileno(),
1990 stdout=devnull.fileno(),
1991 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001992 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001993 if p.returncode == 0:
1994 # Encrypted key with empty string as password.
1995 key_passwords[k] = ''
1996 elif stderr.startswith('Error decrypting key'):
1997 # Definitely encrypted key.
1998 # It would have said "Error reading key" if it didn't parse correctly.
1999 need_passwords.append(k)
2000 else:
2001 # Potentially, a type of key that openssl doesn't understand.
2002 # We'll let the routines in signapk.jar handle it.
2003 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002004 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002005
T.R. Fullhart37e10522013-03-18 10:31:26 -07002006 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002007 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002008 return key_passwords
2009
2010
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002011def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002012 """Gets the minSdkVersion declared in the APK.
2013
changho.shin0f125362019-07-08 10:59:00 +09002014 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002015 This can be both a decimal number (API Level) or a codename.
2016
2017 Args:
2018 apk_name: The APK filename.
2019
2020 Returns:
2021 The parsed SDK version string.
2022
2023 Raises:
2024 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002025 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002026 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002027 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002028 stderr=subprocess.PIPE)
2029 stdoutdata, stderrdata = proc.communicate()
2030 if proc.returncode != 0:
2031 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002032 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002033 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002034
Tao Baof47bf0f2018-03-21 23:28:51 -07002035 for line in stdoutdata.split("\n"):
2036 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002037 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2038 if m:
2039 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002040 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002041
2042
2043def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002044 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002045
Tao Baof47bf0f2018-03-21 23:28:51 -07002046 If minSdkVersion is set to a codename, it is translated to a number using the
2047 provided map.
2048
2049 Args:
2050 apk_name: The APK filename.
2051
2052 Returns:
2053 The parsed SDK version number.
2054
2055 Raises:
2056 ExternalError: On failing to get the min SDK version number.
2057 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002058 version = GetMinSdkVersion(apk_name)
2059 try:
2060 return int(version)
2061 except ValueError:
2062 # Not a decimal number. Codename?
2063 if version in codename_to_api_level_map:
2064 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002065 raise ExternalError(
2066 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2067 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002068
2069
2070def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002071 codename_to_api_level_map=None, whole_file=False,
2072 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002073 """Sign the input_name zip/jar/apk, producing output_name. Use the
2074 given key and password (the latter may be None if the key does not
2075 have a password.
2076
Doug Zongker951495f2009-08-14 12:44:19 -07002077 If whole_file is true, use the "-w" option to SignApk to embed a
2078 signature that covers the whole file in the archive comment of the
2079 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002080
2081 min_api_level is the API Level (int) of the oldest platform this file may end
2082 up on. If not specified for an APK, the API Level is obtained by interpreting
2083 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2084
2085 codename_to_api_level_map is needed to translate the codename which may be
2086 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002087
2088 Caller may optionally specify extra args to be passed to SignApk, which
2089 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002090 """
Tao Bao76def242017-11-21 09:25:31 -08002091 if codename_to_api_level_map is None:
2092 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002093 if extra_signapk_args is None:
2094 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002095
Alex Klyubin9667b182015-12-10 13:38:50 -08002096 java_library_path = os.path.join(
2097 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2098
Tao Baoe95540e2016-11-08 12:08:53 -08002099 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2100 ["-Djava.library.path=" + java_library_path,
2101 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002102 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002103 if whole_file:
2104 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002105
2106 min_sdk_version = min_api_level
2107 if min_sdk_version is None:
2108 if not whole_file:
2109 min_sdk_version = GetMinSdkVersionInt(
2110 input_name, codename_to_api_level_map)
2111 if min_sdk_version is not None:
2112 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2113
T.R. Fullhart37e10522013-03-18 10:31:26 -07002114 cmd.extend([key + OPTIONS.public_key_suffix,
2115 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002116 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002117
Tao Bao73dd4f42018-10-04 16:25:33 -07002118 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002119 if password is not None:
2120 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002121 stdoutdata, _ = proc.communicate(password)
2122 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002123 raise ExternalError(
2124 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002125 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002126
Doug Zongkereef39442009-04-02 12:14:19 -07002127
Doug Zongker37974732010-09-16 17:44:38 -07002128def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002129 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002130
Tao Bao9dd909e2017-11-14 11:27:32 -08002131 For non-AVB images, raise exception if the data is too big. Print a warning
2132 if the data is nearing the maximum size.
2133
2134 For AVB images, the actual image size should be identical to the limit.
2135
2136 Args:
2137 data: A string that contains all the data for the partition.
2138 target: The partition name. The ".img" suffix is optional.
2139 info_dict: The dict to be looked up for relevant info.
2140 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002141 if target.endswith(".img"):
2142 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002143 mount_point = "/" + target
2144
Ying Wangf8824af2014-06-03 14:07:27 -07002145 fs_type = None
2146 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002147 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002148 if mount_point == "/userdata":
2149 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002150 p = info_dict["fstab"][mount_point]
2151 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002152 device = p.device
2153 if "/" in device:
2154 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002155 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002156 if not fs_type or not limit:
2157 return
Doug Zongkereef39442009-04-02 12:14:19 -07002158
Andrew Boie0f9aec82012-02-14 09:32:52 -08002159 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002160 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2161 # path.
2162 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2163 if size != limit:
2164 raise ExternalError(
2165 "Mismatching image size for %s: expected %d actual %d" % (
2166 target, limit, size))
2167 else:
2168 pct = float(size) * 100.0 / limit
2169 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2170 if pct >= 99.0:
2171 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002172
2173 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002174 logger.warning("\n WARNING: %s\n", msg)
2175 else:
2176 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002177
2178
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002179def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002180 """Parses the APK certs info from a given target-files zip.
2181
2182 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2183 tuple with the following elements: (1) a dictionary that maps packages to
2184 certs (based on the "certificate" and "private_key" attributes in the file;
2185 (2) a string representing the extension of compressed APKs in the target files
2186 (e.g ".gz", ".bro").
2187
2188 Args:
2189 tf_zip: The input target_files ZipFile (already open).
2190
2191 Returns:
2192 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2193 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2194 no compressed APKs.
2195 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002196 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002197 compressed_extension = None
2198
Tao Bao0f990332017-09-08 19:02:54 -07002199 # META/apkcerts.txt contains the info for _all_ the packages known at build
2200 # time. Filter out the ones that are not installed.
2201 installed_files = set()
2202 for name in tf_zip.namelist():
2203 basename = os.path.basename(name)
2204 if basename:
2205 installed_files.add(basename)
2206
Tao Baoda30cfa2017-12-01 16:19:46 -08002207 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002208 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002209 if not line:
2210 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002211 m = re.match(
2212 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002213 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2214 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002215 line)
2216 if not m:
2217 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002218
Tao Bao818ddf52018-01-05 11:17:34 -08002219 matches = m.groupdict()
2220 cert = matches["CERT"]
2221 privkey = matches["PRIVKEY"]
2222 name = matches["NAME"]
2223 this_compressed_extension = matches["COMPRESSED"]
2224
2225 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2226 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2227 if cert in SPECIAL_CERT_STRINGS and not privkey:
2228 certmap[name] = cert
2229 elif (cert.endswith(OPTIONS.public_key_suffix) and
2230 privkey.endswith(OPTIONS.private_key_suffix) and
2231 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2232 certmap[name] = cert[:-public_key_suffix_len]
2233 else:
2234 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2235
2236 if not this_compressed_extension:
2237 continue
2238
2239 # Only count the installed files.
2240 filename = name + '.' + this_compressed_extension
2241 if filename not in installed_files:
2242 continue
2243
2244 # Make sure that all the values in the compression map have the same
2245 # extension. We don't support multiple compression methods in the same
2246 # system image.
2247 if compressed_extension:
2248 if this_compressed_extension != compressed_extension:
2249 raise ValueError(
2250 "Multiple compressed extensions: {} vs {}".format(
2251 compressed_extension, this_compressed_extension))
2252 else:
2253 compressed_extension = this_compressed_extension
2254
2255 return (certmap,
2256 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002257
2258
Doug Zongkereef39442009-04-02 12:14:19 -07002259COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002260Global options
2261
2262 -p (--path) <dir>
2263 Prepend <dir>/bin to the list of places to search for binaries run by this
2264 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002265
Doug Zongker05d3dea2009-06-22 11:32:31 -07002266 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002267 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002268
Tao Bao30df8b42018-04-23 15:32:53 -07002269 -x (--extra) <key=value>
2270 Add a key/value pair to the 'extras' dict, which device-specific extension
2271 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002272
Doug Zongkereef39442009-04-02 12:14:19 -07002273 -v (--verbose)
2274 Show command lines being executed.
2275
2276 -h (--help)
2277 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002278
2279 --logfile <file>
2280 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002281"""
2282
Kelvin Zhang0876c412020-06-23 15:06:58 -04002283
Doug Zongkereef39442009-04-02 12:14:19 -07002284def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002285 print(docstring.rstrip("\n"))
2286 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002287
2288
2289def ParseOptions(argv,
2290 docstring,
2291 extra_opts="", extra_long_opts=(),
2292 extra_option_handler=None):
2293 """Parse the options in argv and return any arguments that aren't
2294 flags. docstring is the calling module's docstring, to be displayed
2295 for errors and -h. extra_opts and extra_long_opts are for flags
2296 defined by the caller, which are processed by passing them to
2297 extra_option_handler."""
2298
2299 try:
2300 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002301 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002302 ["help", "verbose", "path=", "signapk_path=",
2303 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002304 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002305 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2306 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002307 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2308 "aftl_key_path=", "aftl_manufacturer_key_path=",
2309 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002310 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002311 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002312 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002313 sys.exit(2)
2314
Doug Zongkereef39442009-04-02 12:14:19 -07002315 for o, a in opts:
2316 if o in ("-h", "--help"):
2317 Usage(docstring)
2318 sys.exit()
2319 elif o in ("-v", "--verbose"):
2320 OPTIONS.verbose = True
2321 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002322 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002323 elif o in ("--signapk_path",):
2324 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002325 elif o in ("--signapk_shared_library_path",):
2326 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002327 elif o in ("--extra_signapk_args",):
2328 OPTIONS.extra_signapk_args = shlex.split(a)
2329 elif o in ("--java_path",):
2330 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002331 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002332 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002333 elif o in ("--android_jar_path",):
2334 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002335 elif o in ("--public_key_suffix",):
2336 OPTIONS.public_key_suffix = a
2337 elif o in ("--private_key_suffix",):
2338 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002339 elif o in ("--boot_signer_path",):
2340 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002341 elif o in ("--boot_signer_args",):
2342 OPTIONS.boot_signer_args = shlex.split(a)
2343 elif o in ("--verity_signer_path",):
2344 OPTIONS.verity_signer_path = a
2345 elif o in ("--verity_signer_args",):
2346 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002347 elif o in ("--aftl_tool_path",):
2348 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002349 elif o in ("--aftl_server",):
2350 OPTIONS.aftl_server = a
2351 elif o in ("--aftl_key_path",):
2352 OPTIONS.aftl_key_path = a
2353 elif o in ("--aftl_manufacturer_key_path",):
2354 OPTIONS.aftl_manufacturer_key_path = a
2355 elif o in ("--aftl_signer_helper",):
2356 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002357 elif o in ("-s", "--device_specific"):
2358 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002359 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002360 key, value = a.split("=", 1)
2361 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002362 elif o in ("--logfile",):
2363 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002364 else:
2365 if extra_option_handler is None or not extra_option_handler(o, a):
2366 assert False, "unknown option \"%s\"" % (o,)
2367
Doug Zongker85448772014-09-09 14:59:20 -07002368 if OPTIONS.search_path:
2369 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2370 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002371
2372 return args
2373
2374
Tao Bao4c851b12016-09-19 13:54:38 -07002375def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002376 """Make a temp file and add it to the list of things to be deleted
2377 when Cleanup() is called. Return the filename."""
2378 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2379 os.close(fd)
2380 OPTIONS.tempfiles.append(fn)
2381 return fn
2382
2383
Tao Bao1c830bf2017-12-25 10:43:47 -08002384def MakeTempDir(prefix='tmp', suffix=''):
2385 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2386
2387 Returns:
2388 The absolute pathname of the new directory.
2389 """
2390 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2391 OPTIONS.tempfiles.append(dir_name)
2392 return dir_name
2393
2394
Doug Zongkereef39442009-04-02 12:14:19 -07002395def Cleanup():
2396 for i in OPTIONS.tempfiles:
2397 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002398 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002399 else:
2400 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002401 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002402
2403
2404class PasswordManager(object):
2405 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002406 self.editor = os.getenv("EDITOR")
2407 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002408
2409 def GetPasswords(self, items):
2410 """Get passwords corresponding to each string in 'items',
2411 returning a dict. (The dict may have keys in addition to the
2412 values in 'items'.)
2413
2414 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2415 user edit that file to add more needed passwords. If no editor is
2416 available, or $ANDROID_PW_FILE isn't define, prompts the user
2417 interactively in the ordinary way.
2418 """
2419
2420 current = self.ReadFile()
2421
2422 first = True
2423 while True:
2424 missing = []
2425 for i in items:
2426 if i not in current or not current[i]:
2427 missing.append(i)
2428 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002429 if not missing:
2430 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002431
2432 for i in missing:
2433 current[i] = ""
2434
2435 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002436 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002437 if sys.version_info[0] >= 3:
2438 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002439 answer = raw_input("try to edit again? [y]> ").strip()
2440 if answer and answer[0] not in 'yY':
2441 raise RuntimeError("key passwords unavailable")
2442 first = False
2443
2444 current = self.UpdateAndReadFile(current)
2445
Kelvin Zhang0876c412020-06-23 15:06:58 -04002446 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002447 """Prompt the user to enter a value (password) for each key in
2448 'current' whose value is fales. Returns a new dict with all the
2449 values.
2450 """
2451 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002452 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002453 if v:
2454 result[k] = v
2455 else:
2456 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002457 result[k] = getpass.getpass(
2458 "Enter password for %s key> " % k).strip()
2459 if result[k]:
2460 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002461 return result
2462
2463 def UpdateAndReadFile(self, current):
2464 if not self.editor or not self.pwfile:
2465 return self.PromptResult(current)
2466
2467 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002468 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002469 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2470 f.write("# (Additional spaces are harmless.)\n\n")
2471
2472 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002473 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002474 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002475 f.write("[[[ %s ]]] %s\n" % (v, k))
2476 if not v and first_line is None:
2477 # position cursor on first line with no password.
2478 first_line = i + 4
2479 f.close()
2480
Tao Bao986ee862018-10-04 15:46:16 -07002481 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002482
2483 return self.ReadFile()
2484
2485 def ReadFile(self):
2486 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002487 if self.pwfile is None:
2488 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002489 try:
2490 f = open(self.pwfile, "r")
2491 for line in f:
2492 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002493 if not line or line[0] == '#':
2494 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002495 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2496 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002497 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002498 else:
2499 result[m.group(2)] = m.group(1)
2500 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002501 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002502 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002503 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002504 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002505
2506
Dan Albert8e0178d2015-01-27 15:53:15 -08002507def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2508 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002509
2510 # http://b/18015246
2511 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2512 # for files larger than 2GiB. We can work around this by adjusting their
2513 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2514 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2515 # it isn't clear to me exactly what circumstances cause this).
2516 # `zipfile.write()` must be used directly to work around this.
2517 #
2518 # This mess can be avoided if we port to python3.
2519 saved_zip64_limit = zipfile.ZIP64_LIMIT
2520 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2521
2522 if compress_type is None:
2523 compress_type = zip_file.compression
2524 if arcname is None:
2525 arcname = filename
2526
2527 saved_stat = os.stat(filename)
2528
2529 try:
2530 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2531 # file to be zipped and reset it when we're done.
2532 os.chmod(filename, perms)
2533
2534 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002535 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2536 # intentional. zip stores datetimes in local time without a time zone
2537 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2538 # in the zip archive.
2539 local_epoch = datetime.datetime.fromtimestamp(0)
2540 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002541 os.utime(filename, (timestamp, timestamp))
2542
2543 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2544 finally:
2545 os.chmod(filename, saved_stat.st_mode)
2546 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2547 zipfile.ZIP64_LIMIT = saved_zip64_limit
2548
2549
Tao Bao58c1b962015-05-20 09:32:18 -07002550def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002551 compress_type=None):
2552 """Wrap zipfile.writestr() function to work around the zip64 limit.
2553
2554 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2555 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2556 when calling crc32(bytes).
2557
2558 But it still works fine to write a shorter string into a large zip file.
2559 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2560 when we know the string won't be too long.
2561 """
2562
2563 saved_zip64_limit = zipfile.ZIP64_LIMIT
2564 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2565
2566 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2567 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002568 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002569 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002570 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002571 else:
Tao Baof3282b42015-04-01 11:21:55 -07002572 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002573 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2574 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2575 # such a case (since
2576 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2577 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2578 # permission bits. We follow the logic in Python 3 to get consistent
2579 # behavior between using the two versions.
2580 if not zinfo.external_attr:
2581 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002582
2583 # If compress_type is given, it overrides the value in zinfo.
2584 if compress_type is not None:
2585 zinfo.compress_type = compress_type
2586
Tao Bao58c1b962015-05-20 09:32:18 -07002587 # If perms is given, it has a priority.
2588 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002589 # If perms doesn't set the file type, mark it as a regular file.
2590 if perms & 0o770000 == 0:
2591 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002592 zinfo.external_attr = perms << 16
2593
Tao Baof3282b42015-04-01 11:21:55 -07002594 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002595 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2596
Dan Albert8b72aef2015-03-23 19:13:21 -07002597 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002598 zipfile.ZIP64_LIMIT = saved_zip64_limit
2599
2600
Tao Bao89d7ab22017-12-14 17:05:33 -08002601def ZipDelete(zip_filename, entries):
2602 """Deletes entries from a ZIP file.
2603
2604 Since deleting entries from a ZIP file is not supported, it shells out to
2605 'zip -d'.
2606
2607 Args:
2608 zip_filename: The name of the ZIP file.
2609 entries: The name of the entry, or the list of names to be deleted.
2610
2611 Raises:
2612 AssertionError: In case of non-zero return from 'zip'.
2613 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002614 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002615 entries = [entries]
2616 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002617 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002618
2619
Tao Baof3282b42015-04-01 11:21:55 -07002620def ZipClose(zip_file):
2621 # http://b/18015246
2622 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2623 # central directory.
2624 saved_zip64_limit = zipfile.ZIP64_LIMIT
2625 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2626
2627 zip_file.close()
2628
2629 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002630
2631
2632class DeviceSpecificParams(object):
2633 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002634
Doug Zongker05d3dea2009-06-22 11:32:31 -07002635 def __init__(self, **kwargs):
2636 """Keyword arguments to the constructor become attributes of this
2637 object, which is passed to all functions in the device-specific
2638 module."""
Tao Bao38884282019-07-10 22:20:56 -07002639 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002640 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002641 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002642
2643 if self.module is None:
2644 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002645 if not path:
2646 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002647 try:
2648 if os.path.isdir(path):
2649 info = imp.find_module("releasetools", [path])
2650 else:
2651 d, f = os.path.split(path)
2652 b, x = os.path.splitext(f)
2653 if x == ".py":
2654 f = b
2655 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002656 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002657 self.module = imp.load_module("device_specific", *info)
2658 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002659 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002660
2661 def _DoCall(self, function_name, *args, **kwargs):
2662 """Call the named function in the device-specific module, passing
2663 the given args and kwargs. The first argument to the call will be
2664 the DeviceSpecific object itself. If there is no module, or the
2665 module does not define the function, return the value of the
2666 'default' kwarg (which itself defaults to None)."""
2667 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002668 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002669 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2670
2671 def FullOTA_Assertions(self):
2672 """Called after emitting the block of assertions at the top of a
2673 full OTA package. Implementations can add whatever additional
2674 assertions they like."""
2675 return self._DoCall("FullOTA_Assertions")
2676
Doug Zongkere5ff5902012-01-17 10:55:37 -08002677 def FullOTA_InstallBegin(self):
2678 """Called at the start of full OTA installation."""
2679 return self._DoCall("FullOTA_InstallBegin")
2680
Yifan Hong10c530d2018-12-27 17:34:18 -08002681 def FullOTA_GetBlockDifferences(self):
2682 """Called during full OTA installation and verification.
2683 Implementation should return a list of BlockDifference objects describing
2684 the update on each additional partitions.
2685 """
2686 return self._DoCall("FullOTA_GetBlockDifferences")
2687
Doug Zongker05d3dea2009-06-22 11:32:31 -07002688 def FullOTA_InstallEnd(self):
2689 """Called at the end of full OTA installation; typically this is
2690 used to install the image for the device's baseband processor."""
2691 return self._DoCall("FullOTA_InstallEnd")
2692
2693 def IncrementalOTA_Assertions(self):
2694 """Called after emitting the block of assertions at the top of an
2695 incremental OTA package. Implementations can add whatever
2696 additional assertions they like."""
2697 return self._DoCall("IncrementalOTA_Assertions")
2698
Doug Zongkere5ff5902012-01-17 10:55:37 -08002699 def IncrementalOTA_VerifyBegin(self):
2700 """Called at the start of the verification phase of incremental
2701 OTA installation; additional checks can be placed here to abort
2702 the script before any changes are made."""
2703 return self._DoCall("IncrementalOTA_VerifyBegin")
2704
Doug Zongker05d3dea2009-06-22 11:32:31 -07002705 def IncrementalOTA_VerifyEnd(self):
2706 """Called at the end of the verification phase of incremental OTA
2707 installation; additional checks can be placed here to abort the
2708 script before any changes are made."""
2709 return self._DoCall("IncrementalOTA_VerifyEnd")
2710
Doug Zongkere5ff5902012-01-17 10:55:37 -08002711 def IncrementalOTA_InstallBegin(self):
2712 """Called at the start of incremental OTA installation (after
2713 verification is complete)."""
2714 return self._DoCall("IncrementalOTA_InstallBegin")
2715
Yifan Hong10c530d2018-12-27 17:34:18 -08002716 def IncrementalOTA_GetBlockDifferences(self):
2717 """Called during incremental OTA installation and verification.
2718 Implementation should return a list of BlockDifference objects describing
2719 the update on each additional partitions.
2720 """
2721 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2722
Doug Zongker05d3dea2009-06-22 11:32:31 -07002723 def IncrementalOTA_InstallEnd(self):
2724 """Called at the end of incremental OTA installation; typically
2725 this is used to install the image for the device's baseband
2726 processor."""
2727 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002728
Tao Bao9bc6bb22015-11-09 16:58:28 -08002729 def VerifyOTA_Assertions(self):
2730 return self._DoCall("VerifyOTA_Assertions")
2731
Tao Bao76def242017-11-21 09:25:31 -08002732
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002733class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002734 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002735 self.name = name
2736 self.data = data
2737 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002738 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002739 self.sha1 = sha1(data).hexdigest()
2740
2741 @classmethod
2742 def FromLocalFile(cls, name, diskname):
2743 f = open(diskname, "rb")
2744 data = f.read()
2745 f.close()
2746 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002747
2748 def WriteToTemp(self):
2749 t = tempfile.NamedTemporaryFile()
2750 t.write(self.data)
2751 t.flush()
2752 return t
2753
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002754 def WriteToDir(self, d):
2755 with open(os.path.join(d, self.name), "wb") as fp:
2756 fp.write(self.data)
2757
Geremy Condra36bd3652014-02-06 19:45:10 -08002758 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002759 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002760
Tao Bao76def242017-11-21 09:25:31 -08002761
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002762DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002763 ".gz": "imgdiff",
2764 ".zip": ["imgdiff", "-z"],
2765 ".jar": ["imgdiff", "-z"],
2766 ".apk": ["imgdiff", "-z"],
2767 ".img": "imgdiff",
2768}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002769
Tao Bao76def242017-11-21 09:25:31 -08002770
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002771class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002772 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002773 self.tf = tf
2774 self.sf = sf
2775 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002776 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002777
2778 def ComputePatch(self):
2779 """Compute the patch (as a string of data) needed to turn sf into
2780 tf. Returns the same tuple as GetPatch()."""
2781
2782 tf = self.tf
2783 sf = self.sf
2784
Doug Zongker24cd2802012-08-14 16:36:15 -07002785 if self.diff_program:
2786 diff_program = self.diff_program
2787 else:
2788 ext = os.path.splitext(tf.name)[1]
2789 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002790
2791 ttemp = tf.WriteToTemp()
2792 stemp = sf.WriteToTemp()
2793
2794 ext = os.path.splitext(tf.name)[1]
2795
2796 try:
2797 ptemp = tempfile.NamedTemporaryFile()
2798 if isinstance(diff_program, list):
2799 cmd = copy.copy(diff_program)
2800 else:
2801 cmd = [diff_program]
2802 cmd.append(stemp.name)
2803 cmd.append(ttemp.name)
2804 cmd.append(ptemp.name)
2805 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002806 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002807
Doug Zongkerf8340082014-08-05 10:39:37 -07002808 def run():
2809 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002810 if e:
2811 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002812 th = threading.Thread(target=run)
2813 th.start()
2814 th.join(timeout=300) # 5 mins
2815 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002816 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002817 p.terminate()
2818 th.join(5)
2819 if th.is_alive():
2820 p.kill()
2821 th.join()
2822
Tianjie Xua2a9f992018-01-05 15:15:54 -08002823 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002824 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002825 self.patch = None
2826 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002827 diff = ptemp.read()
2828 finally:
2829 ptemp.close()
2830 stemp.close()
2831 ttemp.close()
2832
2833 self.patch = diff
2834 return self.tf, self.sf, self.patch
2835
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002836 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002837 """Returns a tuple of (target_file, source_file, patch_data).
2838
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002839 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002840 computing the patch failed.
2841 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002842 return self.tf, self.sf, self.patch
2843
2844
2845def ComputeDifferences(diffs):
2846 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002847 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002848
2849 # Do the largest files first, to try and reduce the long-pole effect.
2850 by_size = [(i.tf.size, i) for i in diffs]
2851 by_size.sort(reverse=True)
2852 by_size = [i[1] for i in by_size]
2853
2854 lock = threading.Lock()
2855 diff_iter = iter(by_size) # accessed under lock
2856
2857 def worker():
2858 try:
2859 lock.acquire()
2860 for d in diff_iter:
2861 lock.release()
2862 start = time.time()
2863 d.ComputePatch()
2864 dur = time.time() - start
2865 lock.acquire()
2866
2867 tf, sf, patch = d.GetPatch()
2868 if sf.name == tf.name:
2869 name = tf.name
2870 else:
2871 name = "%s (%s)" % (tf.name, sf.name)
2872 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002873 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002874 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002875 logger.info(
2876 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2877 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002878 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002879 except Exception:
2880 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002881 raise
2882
2883 # start worker threads; wait for them all to finish.
2884 threads = [threading.Thread(target=worker)
2885 for i in range(OPTIONS.worker_threads)]
2886 for th in threads:
2887 th.start()
2888 while threads:
2889 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002890
2891
Dan Albert8b72aef2015-03-23 19:13:21 -07002892class BlockDifference(object):
2893 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002894 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002895 self.tgt = tgt
2896 self.src = src
2897 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002898 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002899 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002900
Tao Baodd2a5892015-03-12 12:32:37 -07002901 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002902 version = max(
2903 int(i) for i in
2904 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002905 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002906 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002907
Tianjie Xu41976c72019-07-03 13:57:01 -07002908 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2909 version=self.version,
2910 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002911 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002912 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002913 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002914 self.touched_src_ranges = b.touched_src_ranges
2915 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002916
Yifan Hong10c530d2018-12-27 17:34:18 -08002917 # On devices with dynamic partitions, for new partitions,
2918 # src is None but OPTIONS.source_info_dict is not.
2919 if OPTIONS.source_info_dict is None:
2920 is_dynamic_build = OPTIONS.info_dict.get(
2921 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002922 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002923 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002924 is_dynamic_build = OPTIONS.source_info_dict.get(
2925 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002926 is_dynamic_source = partition in shlex.split(
2927 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002928
Yifan Hongbb2658d2019-01-25 12:30:58 -08002929 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002930 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2931
Yifan Hongbb2658d2019-01-25 12:30:58 -08002932 # For dynamic partitions builds, check partition list in both source
2933 # and target build because new partitions may be added, and existing
2934 # partitions may be removed.
2935 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2936
Yifan Hong10c530d2018-12-27 17:34:18 -08002937 if is_dynamic:
2938 self.device = 'map_partition("%s")' % partition
2939 else:
2940 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002941 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2942 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002943 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002944 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2945 OPTIONS.source_info_dict)
2946 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002947
Tao Baod8d14be2016-02-04 14:26:02 -08002948 @property
2949 def required_cache(self):
2950 return self._required_cache
2951
Tao Bao76def242017-11-21 09:25:31 -08002952 def WriteScript(self, script, output_zip, progress=None,
2953 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002954 if not self.src:
2955 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002956 script.Print("Patching %s image unconditionally..." % (self.partition,))
2957 else:
2958 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002959
Dan Albert8b72aef2015-03-23 19:13:21 -07002960 if progress:
2961 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002962 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002963
2964 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002965 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002966
Tao Bao9bc6bb22015-11-09 16:58:28 -08002967 def WriteStrictVerifyScript(self, script):
2968 """Verify all the blocks in the care_map, including clobbered blocks.
2969
2970 This differs from the WriteVerifyScript() function: a) it prints different
2971 error messages; b) it doesn't allow half-way updated images to pass the
2972 verification."""
2973
2974 partition = self.partition
2975 script.Print("Verifying %s..." % (partition,))
2976 ranges = self.tgt.care_map
2977 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002978 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002979 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2980 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002981 self.device, ranges_str,
2982 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002983 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002984 script.AppendExtra("")
2985
Tao Baod522bdc2016-04-12 15:53:16 -07002986 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002987 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002988
2989 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002990 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002991 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002992
2993 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002994 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002995 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002996 ranges = self.touched_src_ranges
2997 expected_sha1 = self.touched_src_sha1
2998 else:
2999 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3000 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003001
3002 # No blocks to be checked, skipping.
3003 if not ranges:
3004 return
3005
Tao Bao5ece99d2015-05-12 11:42:31 -07003006 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003007 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003008 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003009 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3010 '"%s.patch.dat")) then' % (
3011 self.device, ranges_str, expected_sha1,
3012 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003013 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003014 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003015
Tianjie Xufc3422a2015-12-15 11:53:59 -08003016 if self.version >= 4:
3017
3018 # Bug: 21124327
3019 # When generating incrementals for the system and vendor partitions in
3020 # version 4 or newer, explicitly check the first block (which contains
3021 # the superblock) of the partition to see if it's what we expect. If
3022 # this check fails, give an explicit log message about the partition
3023 # having been remounted R/W (the most likely explanation).
3024 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003025 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003026
3027 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003028 if partition == "system":
3029 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3030 else:
3031 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003032 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003033 'ifelse (block_image_recover({device}, "{ranges}") && '
3034 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003035 'package_extract_file("{partition}.transfer.list"), '
3036 '"{partition}.new.dat", "{partition}.patch.dat"), '
3037 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003038 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003039 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003040 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003041
Tao Baodd2a5892015-03-12 12:32:37 -07003042 # Abort the OTA update. Note that the incremental OTA cannot be applied
3043 # even if it may match the checksum of the target partition.
3044 # a) If version < 3, operations like move and erase will make changes
3045 # unconditionally and damage the partition.
3046 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003047 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003048 if partition == "system":
3049 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3050 else:
3051 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3052 script.AppendExtra((
3053 'abort("E%d: %s partition has unexpected contents");\n'
3054 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003055
Yifan Hong10c530d2018-12-27 17:34:18 -08003056 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003057 partition = self.partition
3058 script.Print('Verifying the updated %s image...' % (partition,))
3059 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3060 ranges = self.tgt.care_map
3061 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003062 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003063 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003064 self.device, ranges_str,
3065 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003066
3067 # Bug: 20881595
3068 # Verify that extended blocks are really zeroed out.
3069 if self.tgt.extended:
3070 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003071 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003072 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003073 self.device, ranges_str,
3074 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003075 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003076 if partition == "system":
3077 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3078 else:
3079 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003080 script.AppendExtra(
3081 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003082 ' abort("E%d: %s partition has unexpected non-zero contents after '
3083 'OTA update");\n'
3084 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003085 else:
3086 script.Print('Verified the updated %s image.' % (partition,))
3087
Tianjie Xu209db462016-05-24 17:34:52 -07003088 if partition == "system":
3089 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3090 else:
3091 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3092
Tao Bao5fcaaef2015-06-01 13:40:49 -07003093 script.AppendExtra(
3094 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003095 ' abort("E%d: %s partition has unexpected contents after OTA '
3096 'update");\n'
3097 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003098
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003099 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003100 ZipWrite(output_zip,
3101 '{}.transfer.list'.format(self.path),
3102 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003103
Tao Bao76def242017-11-21 09:25:31 -08003104 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3105 # its size. Quailty 9 almost triples the compression time but doesn't
3106 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003107 # zip | brotli(quality 6) | brotli(quality 9)
3108 # compressed_size: 942M | 869M (~8% reduced) | 854M
3109 # compression_time: 75s | 265s | 719s
3110 # decompression_time: 15s | 25s | 25s
3111
3112 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003113 brotli_cmd = ['brotli', '--quality=6',
3114 '--output={}.new.dat.br'.format(self.path),
3115 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003116 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003117 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003118
3119 new_data_name = '{}.new.dat.br'.format(self.partition)
3120 ZipWrite(output_zip,
3121 '{}.new.dat.br'.format(self.path),
3122 new_data_name,
3123 compress_type=zipfile.ZIP_STORED)
3124 else:
3125 new_data_name = '{}.new.dat'.format(self.partition)
3126 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3127
Dan Albert8e0178d2015-01-27 15:53:15 -08003128 ZipWrite(output_zip,
3129 '{}.patch.dat'.format(self.path),
3130 '{}.patch.dat'.format(self.partition),
3131 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003132
Tianjie Xu209db462016-05-24 17:34:52 -07003133 if self.partition == "system":
3134 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3135 else:
3136 code = ErrorCode.VENDOR_UPDATE_FAILURE
3137
Yifan Hong10c530d2018-12-27 17:34:18 -08003138 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003139 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003140 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003141 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003142 device=self.device, partition=self.partition,
3143 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003144 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003145
Kelvin Zhang0876c412020-06-23 15:06:58 -04003146 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003147 data = source.ReadRangeSet(ranges)
3148 ctx = sha1()
3149
3150 for p in data:
3151 ctx.update(p)
3152
3153 return ctx.hexdigest()
3154
Kelvin Zhang0876c412020-06-23 15:06:58 -04003155 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003156 """Return the hash value for all zero blocks."""
3157 zero_block = '\x00' * 4096
3158 ctx = sha1()
3159 for _ in range(num_blocks):
3160 ctx.update(zero_block)
3161
3162 return ctx.hexdigest()
3163
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003164
Tianjie Xu41976c72019-07-03 13:57:01 -07003165# Expose these two classes to support vendor-specific scripts
3166DataImage = images.DataImage
3167EmptyImage = images.EmptyImage
3168
Tao Bao76def242017-11-21 09:25:31 -08003169
Doug Zongker96a57e72010-09-26 14:57:41 -07003170# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003171PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003172 "ext4": "EMMC",
3173 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003174 "f2fs": "EMMC",
3175 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003176}
Doug Zongker96a57e72010-09-26 14:57:41 -07003177
Kelvin Zhang0876c412020-06-23 15:06:58 -04003178
Yifan Hongbdb32012020-05-07 12:38:53 -07003179def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3180 """
3181 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3182 backwards compatibility. It aborts if the fstab entry has slotselect option
3183 (unless check_no_slot is explicitly set to False).
3184 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003185 fstab = info["fstab"]
3186 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003187 if check_no_slot:
3188 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003189 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003190 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3191 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003192 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003193
3194
Yifan Hongbdb32012020-05-07 12:38:53 -07003195def GetTypeAndDeviceExpr(mount_point, info):
3196 """
3197 Return the filesystem of the partition, and an edify expression that evaluates
3198 to the device at runtime.
3199 """
3200 fstab = info["fstab"]
3201 if fstab:
3202 p = fstab[mount_point]
3203 device_expr = '"%s"' % fstab[mount_point].device
3204 if p.slotselect:
3205 device_expr = 'add_slot_suffix(%s)' % device_expr
3206 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003207 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003208
3209
3210def GetEntryForDevice(fstab, device):
3211 """
3212 Returns:
3213 The first entry in fstab whose device is the given value.
3214 """
3215 if not fstab:
3216 return None
3217 for mount_point in fstab:
3218 if fstab[mount_point].device == device:
3219 return fstab[mount_point]
3220 return None
3221
Kelvin Zhang0876c412020-06-23 15:06:58 -04003222
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003223def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003224 """Parses and converts a PEM-encoded certificate into DER-encoded.
3225
3226 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3227
3228 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003229 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003230 """
3231 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003232 save = False
3233 for line in data.split("\n"):
3234 if "--END CERTIFICATE--" in line:
3235 break
3236 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003237 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003238 if "--BEGIN CERTIFICATE--" in line:
3239 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003240 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003241 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003242
Tao Bao04e1f012018-02-04 12:13:35 -08003243
3244def ExtractPublicKey(cert):
3245 """Extracts the public key (PEM-encoded) from the given certificate file.
3246
3247 Args:
3248 cert: The certificate filename.
3249
3250 Returns:
3251 The public key string.
3252
3253 Raises:
3254 AssertionError: On non-zero return from 'openssl'.
3255 """
3256 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3257 # While openssl 1.1 writes the key into the given filename followed by '-out',
3258 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3259 # stdout instead.
3260 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3261 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3262 pubkey, stderrdata = proc.communicate()
3263 assert proc.returncode == 0, \
3264 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3265 return pubkey
3266
3267
Tao Bao1ac886e2019-06-26 11:58:22 -07003268def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003269 """Extracts the AVB public key from the given public or private key.
3270
3271 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003272 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003273 key: The input key file, which should be PEM-encoded public or private key.
3274
3275 Returns:
3276 The path to the extracted AVB public key file.
3277 """
3278 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3279 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003280 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003281 return output
3282
3283
Doug Zongker412c02f2014-02-13 10:58:24 -08003284def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3285 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003286 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003287
Tao Bao6d5d6232018-03-09 17:04:42 -08003288 Most of the space in the boot and recovery images is just the kernel, which is
3289 identical for the two, so the resulting patch should be efficient. Add it to
3290 the output zip, along with a shell script that is run from init.rc on first
3291 boot to actually do the patching and install the new recovery image.
3292
3293 Args:
3294 input_dir: The top-level input directory of the target-files.zip.
3295 output_sink: The callback function that writes the result.
3296 recovery_img: File object for the recovery image.
3297 boot_img: File objects for the boot image.
3298 info_dict: A dict returned by common.LoadInfoDict() on the input
3299 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003300 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003301 if info_dict is None:
3302 info_dict = OPTIONS.info_dict
3303
Tao Bao6d5d6232018-03-09 17:04:42 -08003304 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003305 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3306
3307 if board_uses_vendorimage:
3308 # In this case, the output sink is rooted at VENDOR
3309 recovery_img_path = "etc/recovery.img"
3310 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3311 sh_dir = "bin"
3312 else:
3313 # In this case the output sink is rooted at SYSTEM
3314 recovery_img_path = "vendor/etc/recovery.img"
3315 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3316 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003317
Tao Baof2cffbd2015-07-22 12:33:18 -07003318 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003319 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003320
3321 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003322 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003323 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003324 # With system-root-image, boot and recovery images will have mismatching
3325 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3326 # to handle such a case.
3327 if system_root_image:
3328 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003329 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003330 assert not os.path.exists(path)
3331 else:
3332 diff_program = ["imgdiff"]
3333 if os.path.exists(path):
3334 diff_program.append("-b")
3335 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003336 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003337 else:
3338 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003339
3340 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3341 _, _, patch = d.ComputePatch()
3342 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003343
Dan Albertebb19aa2015-03-27 19:11:53 -07003344 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003345 # The following GetTypeAndDevice()s need to use the path in the target
3346 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003347 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3348 check_no_slot=False)
3349 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3350 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003351 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003352 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003353
Tao Baof2cffbd2015-07-22 12:33:18 -07003354 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003355
3356 # Note that we use /vendor to refer to the recovery resources. This will
3357 # work for a separate vendor partition mounted at /vendor or a
3358 # /system/vendor subdirectory on the system partition, for which init will
3359 # create a symlink from /vendor to /system/vendor.
3360
3361 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003362if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3363 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003364 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003365 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3366 log -t recovery "Installing new recovery image: succeeded" || \\
3367 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003368else
3369 log -t recovery "Recovery image already installed"
3370fi
3371""" % {'type': recovery_type,
3372 'device': recovery_device,
3373 'sha1': recovery_img.sha1,
3374 'size': recovery_img.size}
3375 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003376 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003377if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3378 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003379 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003380 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3381 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3382 log -t recovery "Installing new recovery image: succeeded" || \\
3383 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003384else
3385 log -t recovery "Recovery image already installed"
3386fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003387""" % {'boot_size': boot_img.size,
3388 'boot_sha1': boot_img.sha1,
3389 'recovery_size': recovery_img.size,
3390 'recovery_sha1': recovery_img.sha1,
3391 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003392 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003393 'recovery_type': recovery_type,
3394 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003395 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003396
Bill Peckhame868aec2019-09-17 17:06:47 -07003397 # The install script location moved from /system/etc to /system/bin in the L
3398 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3399 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003400
Tao Bao32fcdab2018-10-12 10:30:39 -07003401 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003402
Tao Baoda30cfa2017-12-01 16:19:46 -08003403 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003404
3405
3406class DynamicPartitionUpdate(object):
3407 def __init__(self, src_group=None, tgt_group=None, progress=None,
3408 block_difference=None):
3409 self.src_group = src_group
3410 self.tgt_group = tgt_group
3411 self.progress = progress
3412 self.block_difference = block_difference
3413
3414 @property
3415 def src_size(self):
3416 if not self.block_difference:
3417 return 0
3418 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3419
3420 @property
3421 def tgt_size(self):
3422 if not self.block_difference:
3423 return 0
3424 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3425
3426 @staticmethod
3427 def _GetSparseImageSize(img):
3428 if not img:
3429 return 0
3430 return img.blocksize * img.total_blocks
3431
3432
3433class DynamicGroupUpdate(object):
3434 def __init__(self, src_size=None, tgt_size=None):
3435 # None: group does not exist. 0: no size limits.
3436 self.src_size = src_size
3437 self.tgt_size = tgt_size
3438
3439
3440class DynamicPartitionsDifference(object):
3441 def __init__(self, info_dict, block_diffs, progress_dict=None,
3442 source_info_dict=None):
3443 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003444 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003445
3446 self._remove_all_before_apply = False
3447 if source_info_dict is None:
3448 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003449 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003450
Tao Baof1113e92019-06-18 12:10:14 -07003451 block_diff_dict = collections.OrderedDict(
3452 [(e.partition, e) for e in block_diffs])
3453
Yifan Hong10c530d2018-12-27 17:34:18 -08003454 assert len(block_diff_dict) == len(block_diffs), \
3455 "Duplicated BlockDifference object for {}".format(
3456 [partition for partition, count in
3457 collections.Counter(e.partition for e in block_diffs).items()
3458 if count > 1])
3459
Yifan Hong79997e52019-01-23 16:56:19 -08003460 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003461
3462 for p, block_diff in block_diff_dict.items():
3463 self._partition_updates[p] = DynamicPartitionUpdate()
3464 self._partition_updates[p].block_difference = block_diff
3465
3466 for p, progress in progress_dict.items():
3467 if p in self._partition_updates:
3468 self._partition_updates[p].progress = progress
3469
3470 tgt_groups = shlex.split(info_dict.get(
3471 "super_partition_groups", "").strip())
3472 src_groups = shlex.split(source_info_dict.get(
3473 "super_partition_groups", "").strip())
3474
3475 for g in tgt_groups:
3476 for p in shlex.split(info_dict.get(
3477 "super_%s_partition_list" % g, "").strip()):
3478 assert p in self._partition_updates, \
3479 "{} is in target super_{}_partition_list but no BlockDifference " \
3480 "object is provided.".format(p, g)
3481 self._partition_updates[p].tgt_group = g
3482
3483 for g in src_groups:
3484 for p in shlex.split(source_info_dict.get(
3485 "super_%s_partition_list" % g, "").strip()):
3486 assert p in self._partition_updates, \
3487 "{} is in source super_{}_partition_list but no BlockDifference " \
3488 "object is provided.".format(p, g)
3489 self._partition_updates[p].src_group = g
3490
Yifan Hong45433e42019-01-18 13:55:25 -08003491 target_dynamic_partitions = set(shlex.split(info_dict.get(
3492 "dynamic_partition_list", "").strip()))
3493 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3494 if u.tgt_size)
3495 assert block_diffs_with_target == target_dynamic_partitions, \
3496 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3497 list(target_dynamic_partitions), list(block_diffs_with_target))
3498
3499 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3500 "dynamic_partition_list", "").strip()))
3501 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3502 if u.src_size)
3503 assert block_diffs_with_source == source_dynamic_partitions, \
3504 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3505 list(source_dynamic_partitions), list(block_diffs_with_source))
3506
Yifan Hong10c530d2018-12-27 17:34:18 -08003507 if self._partition_updates:
3508 logger.info("Updating dynamic partitions %s",
3509 self._partition_updates.keys())
3510
Yifan Hong79997e52019-01-23 16:56:19 -08003511 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003512
3513 for g in tgt_groups:
3514 self._group_updates[g] = DynamicGroupUpdate()
3515 self._group_updates[g].tgt_size = int(info_dict.get(
3516 "super_%s_group_size" % g, "0").strip())
3517
3518 for g in src_groups:
3519 if g not in self._group_updates:
3520 self._group_updates[g] = DynamicGroupUpdate()
3521 self._group_updates[g].src_size = int(source_info_dict.get(
3522 "super_%s_group_size" % g, "0").strip())
3523
3524 self._Compute()
3525
3526 def WriteScript(self, script, output_zip, write_verify_script=False):
3527 script.Comment('--- Start patching dynamic partitions ---')
3528 for p, u in self._partition_updates.items():
3529 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3530 script.Comment('Patch partition %s' % p)
3531 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3532 write_verify_script=False)
3533
3534 op_list_path = MakeTempFile()
3535 with open(op_list_path, 'w') as f:
3536 for line in self._op_list:
3537 f.write('{}\n'.format(line))
3538
3539 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3540
3541 script.Comment('Update dynamic partition metadata')
3542 script.AppendExtra('assert(update_dynamic_partitions('
3543 'package_extract_file("dynamic_partitions_op_list")));')
3544
3545 if write_verify_script:
3546 for p, u in self._partition_updates.items():
3547 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3548 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003549 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003550
3551 for p, u in self._partition_updates.items():
3552 if u.tgt_size and u.src_size <= u.tgt_size:
3553 script.Comment('Patch partition %s' % p)
3554 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3555 write_verify_script=write_verify_script)
3556 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003557 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003558
3559 script.Comment('--- End patching dynamic partitions ---')
3560
3561 def _Compute(self):
3562 self._op_list = list()
3563
3564 def append(line):
3565 self._op_list.append(line)
3566
3567 def comment(line):
3568 self._op_list.append("# %s" % line)
3569
3570 if self._remove_all_before_apply:
3571 comment('Remove all existing dynamic partitions and groups before '
3572 'applying full OTA')
3573 append('remove_all_groups')
3574
3575 for p, u in self._partition_updates.items():
3576 if u.src_group and not u.tgt_group:
3577 append('remove %s' % p)
3578
3579 for p, u in self._partition_updates.items():
3580 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3581 comment('Move partition %s from %s to default' % (p, u.src_group))
3582 append('move %s default' % p)
3583
3584 for p, u in self._partition_updates.items():
3585 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3586 comment('Shrink partition %s from %d to %d' %
3587 (p, u.src_size, u.tgt_size))
3588 append('resize %s %s' % (p, u.tgt_size))
3589
3590 for g, u in self._group_updates.items():
3591 if u.src_size is not None and u.tgt_size is None:
3592 append('remove_group %s' % g)
3593 if (u.src_size is not None and u.tgt_size is not None and
3594 u.src_size > u.tgt_size):
3595 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3596 append('resize_group %s %d' % (g, u.tgt_size))
3597
3598 for g, u in self._group_updates.items():
3599 if u.src_size is None and u.tgt_size is not None:
3600 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3601 append('add_group %s %d' % (g, u.tgt_size))
3602 if (u.src_size is not None and u.tgt_size is not None and
3603 u.src_size < u.tgt_size):
3604 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3605 append('resize_group %s %d' % (g, u.tgt_size))
3606
3607 for p, u in self._partition_updates.items():
3608 if u.tgt_group and not u.src_group:
3609 comment('Add partition %s to group %s' % (p, u.tgt_group))
3610 append('add %s %s' % (p, u.tgt_group))
3611
3612 for p, u in self._partition_updates.items():
3613 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003614 comment('Grow partition %s from %d to %d' %
3615 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003616 append('resize %s %d' % (p, u.tgt_size))
3617
3618 for p, u in self._partition_updates.items():
3619 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3620 comment('Move partition %s from default to %s' %
3621 (p, u.tgt_group))
3622 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003623
3624
Yifan Hong85ac5012021-01-07 14:43:46 -08003625def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003626 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003627 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003628
3629 Args:
3630 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3631
3632 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003633 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003634 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003635 tmp_dir = MakeTempDir('boot_', suffix='.img')
3636 try:
3637 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3638 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3639 if not os.path.isfile(ramdisk):
3640 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3641 return None
3642 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3643 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3644
3645 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3646 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3647 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3648 # the host environment.
3649 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3650 cwd=extracted_ramdisk)
3651
Yifan Hongc65a0542021-01-07 14:21:01 -08003652 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3653 prop_file = os.path.join(extracted_ramdisk, search_path)
3654 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003655 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003656 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3657
Yifan Hong7dc51172021-01-12 11:27:39 -08003658 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003659
Yifan Hong85ac5012021-01-07 14:43:46 -08003660 except ExternalError as e:
3661 logger.warning('Unable to get boot image build props: %s', e)
3662 return None
3663
3664
3665def GetBootImageTimestamp(boot_img):
3666 """
3667 Get timestamp from ramdisk within the boot image
3668
3669 Args:
3670 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3671
3672 Return:
3673 An integer that corresponds to the timestamp of the boot image, or None
3674 if file has unknown format. Raise exception if an unexpected error has
3675 occurred.
3676 """
3677 prop_file = GetBootImageBuildProp(boot_img)
3678 if not prop_file:
3679 return None
3680
3681 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3682 if props is None:
3683 return None
3684
3685 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003686 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3687 if timestamp:
3688 return int(timestamp)
3689 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3690 return None
3691
3692 except ExternalError as e:
3693 logger.warning('Unable to get boot image timestamp: %s', e)
3694 return None