blob: 414ab9748571d7528a77372ed84ef2731922b7ad [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
Tianjiebf0b8a82021-03-03 17:31:04 -0800112# descriptor into vbmeta.img. When adding a new entry here, the
113# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
114# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000115AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
116 'system', 'system_ext', 'vendor', 'vendor_boot',
117 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800118
Tao Bao08c190f2019-06-03 23:07:58 -0700119# Chained VBMeta partitions.
120AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
121
Tianjie Xu861f4132018-09-12 11:49:33 -0700122# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400123PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700124 'system',
125 'vendor',
126 'product',
127 'system_ext',
128 'odm',
129 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700130 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400131]
Tianjie Xu861f4132018-09-12 11:49:33 -0700132
Yifan Hong5057b952021-01-07 14:09:57 -0800133# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800134PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800135
Yifan Hongc65a0542021-01-07 14:21:01 -0800136# See sysprop.mk. If file is moved, add new search paths here; don't remove
137# existing search paths.
138RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700139
Tianjie Xu209db462016-05-24 17:34:52 -0700140class ErrorCode(object):
141 """Define error_codes for failures that happen during the actual
142 update package installation.
143
144 Error codes 0-999 are reserved for failures before the package
145 installation (i.e. low battery, package verification failure).
146 Detailed code in 'bootable/recovery/error_code.h' """
147
148 SYSTEM_VERIFICATION_FAILURE = 1000
149 SYSTEM_UPDATE_FAILURE = 1001
150 SYSTEM_UNEXPECTED_CONTENTS = 1002
151 SYSTEM_NONZERO_CONTENTS = 1003
152 SYSTEM_RECOVER_FAILURE = 1004
153 VENDOR_VERIFICATION_FAILURE = 2000
154 VENDOR_UPDATE_FAILURE = 2001
155 VENDOR_UNEXPECTED_CONTENTS = 2002
156 VENDOR_NONZERO_CONTENTS = 2003
157 VENDOR_RECOVER_FAILURE = 2004
158 OEM_PROP_MISMATCH = 3000
159 FINGERPRINT_MISMATCH = 3001
160 THUMBPRINT_MISMATCH = 3002
161 OLDER_BUILD = 3003
162 DEVICE_MISMATCH = 3004
163 BAD_PATCH_FILE = 3005
164 INSUFFICIENT_CACHE_SPACE = 3006
165 TUNE_PARTITION_FAILURE = 3007
166 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800167
Tao Bao80921982018-03-21 21:02:19 -0700168
Dan Albert8b72aef2015-03-23 19:13:21 -0700169class ExternalError(RuntimeError):
170 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700171
172
Tao Bao32fcdab2018-10-12 10:30:39 -0700173def InitLogging():
174 DEFAULT_LOGGING_CONFIG = {
175 'version': 1,
176 'disable_existing_loggers': False,
177 'formatters': {
178 'standard': {
179 'format':
180 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
181 'datefmt': '%Y-%m-%d %H:%M:%S',
182 },
183 },
184 'handlers': {
185 'default': {
186 'class': 'logging.StreamHandler',
187 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700188 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700189 },
190 },
191 'loggers': {
192 '': {
193 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700195 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 }
197 }
198 }
199 env_config = os.getenv('LOGGING_CONFIG')
200 if env_config:
201 with open(env_config) as f:
202 config = json.load(f)
203 else:
204 config = DEFAULT_LOGGING_CONFIG
205
206 # Increase the logging level for verbose mode.
207 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700208 config = copy.deepcopy(config)
209 config['handlers']['default']['level'] = 'INFO'
210
211 if OPTIONS.logfile:
212 config = copy.deepcopy(config)
213 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400214 'class': 'logging.FileHandler',
215 'formatter': 'standard',
216 'level': 'INFO',
217 'mode': 'w',
218 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700219 }
220 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700221
222 logging.config.dictConfig(config)
223
224
Yifan Hong8e332ff2020-07-29 17:51:55 -0700225def SetHostToolLocation(tool_name, location):
226 OPTIONS.host_tools[tool_name] = location
227
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900228def FindHostToolPath(tool_name):
229 """Finds the path to the host tool.
230
231 Args:
232 tool_name: name of the tool to find
233 Returns:
234 path to the tool if found under either one of the host_tools map or under
235 the same directory as this binary is located at. If not found, tool_name
236 is returned.
237 """
238 if tool_name in OPTIONS.host_tools:
239 return OPTIONS.host_tools[tool_name]
240
241 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
242 tool_path = os.path.join(my_dir, tool_name)
243 if os.path.exists(tool_path):
244 return tool_path
245
246 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700247
Tao Bao39451582017-05-04 11:10:47 -0700248def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700250
Tao Bao73dd4f42018-10-04 16:25:33 -0700251 Args:
252 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700253 verbose: Whether the commands should be shown. Default to the global
254 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
256 stdin, etc. stdout and stderr will default to subprocess.PIPE and
257 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800258 universal_newlines will default to True, as most of the users in
259 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700260
261 Returns:
262 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700263 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700264 if 'stdout' not in kwargs and 'stderr' not in kwargs:
265 kwargs['stdout'] = subprocess.PIPE
266 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800267 if 'universal_newlines' not in kwargs:
268 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700269
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900270 if args:
271 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900273 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700274
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400276 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700277 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700278 return subprocess.Popen(args, **kwargs)
279
280
Tao Bao986ee862018-10-04 15:46:16 -0700281def RunAndCheckOutput(args, verbose=None, **kwargs):
282 """Runs the given command and returns the output.
283
284 Args:
285 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700286 verbose: Whether the commands should be shown. Default to the global
287 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700288 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
289 stdin, etc. stdout and stderr will default to subprocess.PIPE and
290 subprocess.STDOUT respectively unless caller specifies any of them.
291
292 Returns:
293 The output string.
294
295 Raises:
296 ExternalError: On non-zero exit from the command.
297 """
Tao Bao986ee862018-10-04 15:46:16 -0700298 proc = Run(args, verbose=verbose, **kwargs)
299 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800300 if output is None:
301 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400303 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700304 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700305 if proc.returncode != 0:
306 raise ExternalError(
307 "Failed to run command '{}' (exit code {}):\n{}".format(
308 args, proc.returncode, output))
309 return output
310
311
Tao Baoc765cca2018-01-31 17:32:40 -0800312def RoundUpTo4K(value):
313 rounded_up = value + 4095
314 return rounded_up - (rounded_up % 4096)
315
316
Ying Wang7e6d4e42010-12-13 16:25:36 -0800317def CloseInheritedPipes():
318 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
319 before doing other work."""
320 if platform.system() != "Darwin":
321 return
322 for d in range(3, 1025):
323 try:
324 stat = os.fstat(d)
325 if stat is not None:
326 pipebit = stat[0] & 0x1000
327 if pipebit != 0:
328 os.close(d)
329 except OSError:
330 pass
331
332
Tao Bao1c320f82019-10-04 23:25:12 -0700333class BuildInfo(object):
334 """A class that holds the information for a given build.
335
336 This class wraps up the property querying for a given source or target build.
337 It abstracts away the logic of handling OEM-specific properties, and caches
338 the commonly used properties such as fingerprint.
339
340 There are two types of info dicts: a) build-time info dict, which is generated
341 at build time (i.e. included in a target_files zip); b) OEM info dict that is
342 specified at package generation time (via command line argument
343 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
344 having "oem_fingerprint_properties" in build-time info dict), all the queries
345 would be answered based on build-time info dict only. Otherwise if using
346 OEM-specific properties, some of them will be calculated from two info dicts.
347
348 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800349 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700350
351 Attributes:
352 info_dict: The build-time info dict.
353 is_ab: Whether it's a build that uses A/B OTA.
354 oem_dicts: A list of OEM dicts.
355 oem_props: A list of OEM properties that should be read from OEM dicts; None
356 if the build doesn't use any OEM-specific property.
357 fingerprint: The fingerprint of the build, which would be calculated based
358 on OEM properties if applicable.
359 device: The device name, which could come from OEM dicts if applicable.
360 """
361
362 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
363 "ro.product.manufacturer", "ro.product.model",
364 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
366 "product", "odm", "vendor", "system_ext", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
368 "product", "product_services", "odm", "vendor", "system"]
369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700370
Tao Bao3ed35d32019-10-07 20:48:48 -0700371 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700372 """Initializes a BuildInfo instance with the given dicts.
373
374 Note that it only wraps up the given dicts, without making copies.
375
376 Arguments:
377 info_dict: The build-time info dict.
378 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
379 that it always uses the first dict to calculate the fingerprint or the
380 device name. The rest would be used for asserting OEM properties only
381 (e.g. one package can be installed on one of these devices).
382
383 Raises:
384 ValueError: On invalid inputs.
385 """
386 self.info_dict = info_dict
387 self.oem_dicts = oem_dicts
388
389 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700390
Hongguang Chend7c160f2020-05-03 21:24:26 -0700391 # Skip _oem_props if oem_dicts is None to use BuildInfo in
392 # sign_target_files_apks
393 if self.oem_dicts:
394 self._oem_props = info_dict.get("oem_fingerprint_properties")
395 else:
396 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700397
Daniel Normand5fe8622020-01-08 17:01:11 -0800398 def check_fingerprint(fingerprint):
399 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
400 raise ValueError(
401 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
402 "3.2.2. Build Parameters.".format(fingerprint))
403
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800405 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800406 try:
407 fingerprint = self.CalculatePartitionFingerprint(partition)
408 check_fingerprint(fingerprint)
409 self._partition_fingerprints[partition] = fingerprint
410 except ExternalError:
411 continue
412 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800413 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800414 # need a fingerprint when creating the image.
415 self._partition_fingerprints[
416 "system_other"] = self._partition_fingerprints["system"]
417
Tao Bao1c320f82019-10-04 23:25:12 -0700418 # These two should be computed only after setting self._oem_props.
419 self._device = self.GetOemProperty("ro.product.device")
420 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800421 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700422
423 @property
424 def is_ab(self):
425 return self._is_ab
426
427 @property
428 def device(self):
429 return self._device
430
431 @property
432 def fingerprint(self):
433 return self._fingerprint
434
435 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700436 def oem_props(self):
437 return self._oem_props
438
439 def __getitem__(self, key):
440 return self.info_dict[key]
441
442 def __setitem__(self, key, value):
443 self.info_dict[key] = value
444
445 def get(self, key, default=None):
446 return self.info_dict.get(key, default)
447
448 def items(self):
449 return self.info_dict.items()
450
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000451 def _GetRawBuildProp(self, prop, partition):
452 prop_file = '{}.build.prop'.format(
453 partition) if partition else 'build.prop'
454 partition_props = self.info_dict.get(prop_file)
455 if not partition_props:
456 return None
457 return partition_props.GetProp(prop)
458
Daniel Normand5fe8622020-01-08 17:01:11 -0800459 def GetPartitionBuildProp(self, prop, partition):
460 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800461
462 # Boot image uses ro.[product.]bootimage instead of boot.
463 prop_partition = "bootimage" if partition == "boot" else partition
464
Daniel Normand5fe8622020-01-08 17:01:11 -0800465 # If provided a partition for this property, only look within that
466 # partition's build.prop.
467 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800468 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800469 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800470 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000471
472 prop_val = self._GetRawBuildProp(prop, partition)
473 if prop_val is not None:
474 return prop_val
475 raise ExternalError("couldn't find %s in %s.build.prop" %
476 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800477
Tao Bao1c320f82019-10-04 23:25:12 -0700478 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800479 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700480 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
481 return self._ResolveRoProductBuildProp(prop)
482
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000483 prop_val = self._GetRawBuildProp(prop, None)
484 if prop_val is not None:
485 return prop_val
486
487 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700488
489 def _ResolveRoProductBuildProp(self, prop):
490 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700492 if prop_val:
493 return prop_val
494
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000496 source_order_val = self._GetRawBuildProp(
497 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700498 if source_order_val:
499 source_order = source_order_val.split(",")
500 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700501 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700502
503 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700504 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700505 raise ExternalError(
506 "Invalid ro.product.property_source_order '{}'".format(source_order))
507
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700509 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000510 "ro.product", "ro.product.{}".format(source_partition), 1)
511 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700512 if prop_val:
513 return prop_val
514
515 raise ExternalError("couldn't resolve {}".format(prop))
516
Steven Laver8e2086e2020-04-27 16:26:31 -0700517 def _GetRoProductPropsDefaultSourceOrder(self):
518 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
519 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000520 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000522 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700523 if android_version == "10":
524 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
525 # NOTE: float() conversion of android_version will have rounding error.
526 # We are checking for "9" or less, and using "< 10" is well outside of
527 # possible floating point rounding.
528 try:
529 android_version_val = float(android_version)
530 except ValueError:
531 android_version_val = 0
532 if android_version_val < 10:
533 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
534 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
535
Tianjieb37c5be2020-10-15 21:27:10 -0700536 def _GetPlatformVersion(self):
537 version_sdk = self.GetBuildProp("ro.build.version.sdk")
538 # init code switches to version_release_or_codename (see b/158483506). After
539 # API finalization, release_or_codename will be the same as release. This
540 # is the best effort to support pre-S dev stage builds.
541 if int(version_sdk) >= 30:
542 try:
543 return self.GetBuildProp("ro.build.version.release_or_codename")
544 except ExternalError:
545 logger.warning('Failed to find ro.build.version.release_or_codename')
546
547 return self.GetBuildProp("ro.build.version.release")
548
549 def _GetPartitionPlatformVersion(self, partition):
550 try:
551 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
552 partition)
553 except ExternalError:
554 return self.GetPartitionBuildProp("ro.build.version.release",
555 partition)
556
Tao Bao1c320f82019-10-04 23:25:12 -0700557 def GetOemProperty(self, key):
558 if self.oem_props is not None and key in self.oem_props:
559 return self.oem_dicts[0][key]
560 return self.GetBuildProp(key)
561
Daniel Normand5fe8622020-01-08 17:01:11 -0800562 def GetPartitionFingerprint(self, partition):
563 return self._partition_fingerprints.get(partition, None)
564
565 def CalculatePartitionFingerprint(self, partition):
566 try:
567 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
568 except ExternalError:
569 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
570 self.GetPartitionBuildProp("ro.product.brand", partition),
571 self.GetPartitionBuildProp("ro.product.name", partition),
572 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700573 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800574 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400575 self.GetPartitionBuildProp(
576 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800577 self.GetPartitionBuildProp("ro.build.type", partition),
578 self.GetPartitionBuildProp("ro.build.tags", partition))
579
Tao Bao1c320f82019-10-04 23:25:12 -0700580 def CalculateFingerprint(self):
581 if self.oem_props is None:
582 try:
583 return self.GetBuildProp("ro.build.fingerprint")
584 except ExternalError:
585 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
586 self.GetBuildProp("ro.product.brand"),
587 self.GetBuildProp("ro.product.name"),
588 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700589 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700590 self.GetBuildProp("ro.build.id"),
591 self.GetBuildProp("ro.build.version.incremental"),
592 self.GetBuildProp("ro.build.type"),
593 self.GetBuildProp("ro.build.tags"))
594 return "%s/%s/%s:%s" % (
595 self.GetOemProperty("ro.product.brand"),
596 self.GetOemProperty("ro.product.name"),
597 self.GetOemProperty("ro.product.device"),
598 self.GetBuildProp("ro.build.thumbprint"))
599
600 def WriteMountOemScript(self, script):
601 assert self.oem_props is not None
602 recovery_mount_options = self.info_dict.get("recovery_mount_options")
603 script.Mount("/oem", recovery_mount_options)
604
605 def WriteDeviceAssertions(self, script, oem_no_mount):
606 # Read the property directly if not using OEM properties.
607 if not self.oem_props:
608 script.AssertDevice(self.device)
609 return
610
611 # Otherwise assert OEM properties.
612 if not self.oem_dicts:
613 raise ExternalError(
614 "No OEM file provided to answer expected assertions")
615
616 for prop in self.oem_props.split():
617 values = []
618 for oem_dict in self.oem_dicts:
619 if prop in oem_dict:
620 values.append(oem_dict[prop])
621 if not values:
622 raise ExternalError(
623 "The OEM file is missing the property %s" % (prop,))
624 script.AssertOemProperty(prop, values, oem_no_mount)
625
626
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000627def ReadFromInputFile(input_file, fn):
628 """Reads the contents of fn from input zipfile or directory."""
629 if isinstance(input_file, zipfile.ZipFile):
630 return input_file.read(fn).decode()
631 else:
632 path = os.path.join(input_file, *fn.split("/"))
633 try:
634 with open(path) as f:
635 return f.read()
636 except IOError as e:
637 if e.errno == errno.ENOENT:
638 raise KeyError(fn)
639
640
Yifan Hong10482a22021-01-07 14:38:41 -0800641def ExtractFromInputFile(input_file, fn):
642 """Extracts the contents of fn from input zipfile or directory into a file."""
643 if isinstance(input_file, zipfile.ZipFile):
644 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500645 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800646 f.write(input_file.read(fn))
647 return tmp_file
648 else:
649 file = os.path.join(input_file, *fn.split("/"))
650 if not os.path.exists(file):
651 raise KeyError(fn)
652 return file
653
654
Tao Bao410ad8b2018-08-24 12:08:38 -0700655def LoadInfoDict(input_file, repacking=False):
656 """Loads the key/value pairs from the given input target_files.
657
Tianjiea85bdf02020-07-29 11:56:19 -0700658 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700659 checks and returns the parsed key/value pairs for to the given build. It's
660 usually called early when working on input target_files files, e.g. when
661 generating OTAs, or signing builds. Note that the function may be called
662 against an old target_files file (i.e. from past dessert releases). So the
663 property parsing needs to be backward compatible.
664
665 In a `META/misc_info.txt`, a few properties are stored as links to the files
666 in the PRODUCT_OUT directory. It works fine with the build system. However,
667 they are no longer available when (re)generating images from target_files zip.
668 When `repacking` is True, redirect these properties to the actual files in the
669 unzipped directory.
670
671 Args:
672 input_file: The input target_files file, which could be an open
673 zipfile.ZipFile instance, or a str for the dir that contains the files
674 unzipped from a target_files file.
675 repacking: Whether it's trying repack an target_files file after loading the
676 info dict (default: False). If so, it will rewrite a few loaded
677 properties (e.g. selinux_fc, root_dir) to point to the actual files in
678 target_files file. When doing repacking, `input_file` must be a dir.
679
680 Returns:
681 A dict that contains the parsed key/value pairs.
682
683 Raises:
684 AssertionError: On invalid input arguments.
685 ValueError: On malformed input values.
686 """
687 if repacking:
688 assert isinstance(input_file, str), \
689 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700690
Doug Zongkerc9253822014-02-04 12:17:58 -0800691 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000692 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800693
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700694 try:
Michael Runge6e836112014-04-15 17:40:21 -0700695 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700696 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700697 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700698
Tao Bao410ad8b2018-08-24 12:08:38 -0700699 if "recovery_api_version" not in d:
700 raise ValueError("Failed to find 'recovery_api_version'")
701 if "fstab_version" not in d:
702 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800703
Tao Bao410ad8b2018-08-24 12:08:38 -0700704 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700705 # "selinux_fc" properties should point to the file_contexts files
706 # (file_contexts.bin) under META/.
707 for key in d:
708 if key.endswith("selinux_fc"):
709 fc_basename = os.path.basename(d[key])
710 fc_config = os.path.join(input_file, "META", fc_basename)
711 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700712
Daniel Norman72c626f2019-05-13 15:58:14 -0700713 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700714
Tom Cherryd14b8952018-08-09 14:26:00 -0700715 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700716 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700717 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700718 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700719
David Anderson0ec64ac2019-12-06 12:21:18 -0800720 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700721 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700722 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800723 key_name = part_name + "_base_fs_file"
724 if key_name not in d:
725 continue
726 basename = os.path.basename(d[key_name])
727 base_fs_file = os.path.join(input_file, "META", basename)
728 if os.path.exists(base_fs_file):
729 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700730 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700731 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800732 "Failed to find %s base fs file: %s", part_name, base_fs_file)
733 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700734
Doug Zongker37974732010-09-16 17:44:38 -0700735 def makeint(key):
736 if key in d:
737 d[key] = int(d[key], 0)
738
739 makeint("recovery_api_version")
740 makeint("blocksize")
741 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700742 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700743 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700744 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700745 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800746 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700747
Steve Muckle903a1ca2020-05-07 17:32:10 -0700748 boot_images = "boot.img"
749 if "boot_images" in d:
750 boot_images = d["boot_images"]
751 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400752 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700753
Tao Bao765668f2019-10-04 22:03:00 -0700754 # Load recovery fstab if applicable.
755 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800756
Tianjie Xu861f4132018-09-12 11:49:33 -0700757 # Tries to load the build props for all partitions with care_map, including
758 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800759 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800760 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000761 d[partition_prop] = PartitionBuildProps.FromInputFile(
762 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700763 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800764
Tao Bao3ed35d32019-10-07 20:48:48 -0700765 # Set up the salt (based on fingerprint) that will be used when adding AVB
766 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800767 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700768 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800769 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800770 fingerprint = build_info.GetPartitionFingerprint(partition)
771 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400772 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400773 try:
774 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
775 except KeyError:
776 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700777 return d
778
Tao Baod1de6f32017-03-01 16:38:48 -0800779
Kelvin Zhang39aea442020-08-17 11:04:25 -0400780
Daniel Norman4cc9df62019-07-18 10:11:07 -0700781def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900782 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700783 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900784
Daniel Norman4cc9df62019-07-18 10:11:07 -0700785
786def LoadDictionaryFromFile(file_path):
787 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900788 return LoadDictionaryFromLines(lines)
789
790
Michael Runge6e836112014-04-15 17:40:21 -0700791def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700792 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700793 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700794 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700795 if not line or line.startswith("#"):
796 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700797 if "=" in line:
798 name, value = line.split("=", 1)
799 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700800 return d
801
Tao Baod1de6f32017-03-01 16:38:48 -0800802
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000803class PartitionBuildProps(object):
804 """The class holds the build prop of a particular partition.
805
806 This class loads the build.prop and holds the build properties for a given
807 partition. It also partially recognizes the 'import' statement in the
808 build.prop; and calculates alternative values of some specific build
809 properties during runtime.
810
811 Attributes:
812 input_file: a zipped target-file or an unzipped target-file directory.
813 partition: name of the partition.
814 props_allow_override: a list of build properties to search for the
815 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000816 build_props: a dict of build properties for the given partition.
817 prop_overrides: a set of props that are overridden by import.
818 placeholder_values: A dict of runtime variables' values to replace the
819 placeholders in the build.prop file. We expect exactly one value for
820 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000821 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400822
Tianjie Xu9afb2212020-05-10 21:48:15 +0000823 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000824 self.input_file = input_file
825 self.partition = name
826 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000828 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000829 self.prop_overrides = set()
830 self.placeholder_values = {}
831 if placeholder_values:
832 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000833
834 @staticmethod
835 def FromDictionary(name, build_props):
836 """Constructs an instance from a build prop dictionary."""
837
838 props = PartitionBuildProps("unknown", name)
839 props.build_props = build_props.copy()
840 return props
841
842 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000843 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000844 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800845
846 if name == "boot":
847 data = PartitionBuildProps._ReadBootPropFile(input_file)
848 else:
849 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
850
851 props = PartitionBuildProps(input_file, name, placeholder_values)
852 props._LoadBuildProp(data)
853 return props
854
855 @staticmethod
856 def _ReadBootPropFile(input_file):
857 """
858 Read build.prop for boot image from input_file.
859 Return empty string if not found.
860 """
861 try:
862 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
863 except KeyError:
864 logger.warning('Failed to read IMAGES/boot.img')
865 return ''
866 prop_file = GetBootImageBuildProp(boot_img)
867 if prop_file is None:
868 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500869 with open(prop_file, "r") as f:
870 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800871
872 @staticmethod
873 def _ReadPartitionPropFile(input_file, name):
874 """
875 Read build.prop for name from input_file.
876 Return empty string if not found.
877 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000878 data = ''
879 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
880 '{}/build.prop'.format(name.upper())]:
881 try:
882 data = ReadFromInputFile(input_file, prop_file)
883 break
884 except KeyError:
885 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800886 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000887
Yifan Hong125d0b62020-09-24 17:07:03 -0700888 @staticmethod
889 def FromBuildPropFile(name, build_prop_file):
890 """Constructs an instance from a build prop file."""
891
892 props = PartitionBuildProps("unknown", name)
893 with open(build_prop_file) as f:
894 props._LoadBuildProp(f.read())
895 return props
896
Tianjie Xu9afb2212020-05-10 21:48:15 +0000897 def _LoadBuildProp(self, data):
898 for line in data.split('\n'):
899 line = line.strip()
900 if not line or line.startswith("#"):
901 continue
902 if line.startswith("import"):
903 overrides = self._ImportParser(line)
904 duplicates = self.prop_overrides.intersection(overrides.keys())
905 if duplicates:
906 raise ValueError('prop {} is overridden multiple times'.format(
907 ','.join(duplicates)))
908 self.prop_overrides = self.prop_overrides.union(overrides.keys())
909 self.build_props.update(overrides)
910 elif "=" in line:
911 name, value = line.split("=", 1)
912 if name in self.prop_overrides:
913 raise ValueError('prop {} is set again after overridden by import '
914 'statement'.format(name))
915 self.build_props[name] = value
916
917 def _ImportParser(self, line):
918 """Parses the build prop in a given import statement."""
919
920 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400921 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000922 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700923
924 if len(tokens) == 3:
925 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
926 return {}
927
Tianjie Xu9afb2212020-05-10 21:48:15 +0000928 import_path = tokens[1]
929 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
930 raise ValueError('Unrecognized import path {}'.format(line))
931
932 # We only recognize a subset of import statement that the init process
933 # supports. And we can loose the restriction based on how the dynamic
934 # fingerprint is used in practice. The placeholder format should be
935 # ${placeholder}, and its value should be provided by the caller through
936 # the placeholder_values.
937 for prop, value in self.placeholder_values.items():
938 prop_place_holder = '${{{}}}'.format(prop)
939 if prop_place_holder in import_path:
940 import_path = import_path.replace(prop_place_holder, value)
941 if '$' in import_path:
942 logger.info('Unresolved place holder in import path %s', import_path)
943 return {}
944
945 import_path = import_path.replace('/{}'.format(self.partition),
946 self.partition.upper())
947 logger.info('Parsing build props override from %s', import_path)
948
949 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
950 d = LoadDictionaryFromLines(lines)
951 return {key: val for key, val in d.items()
952 if key in self.props_allow_override}
953
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000954 def GetProp(self, prop):
955 return self.build_props.get(prop)
956
957
Tianjie Xucfa86222016-03-07 16:31:19 -0800958def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
959 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700960 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700961 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700962 self.mount_point = mount_point
963 self.fs_type = fs_type
964 self.device = device
965 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700966 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700967 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700968
969 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800970 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700971 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700972 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700973 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700974
Tao Baod1de6f32017-03-01 16:38:48 -0800975 assert fstab_version == 2
976
977 d = {}
978 for line in data.split("\n"):
979 line = line.strip()
980 if not line or line.startswith("#"):
981 continue
982
983 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
984 pieces = line.split()
985 if len(pieces) != 5:
986 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
987
988 # Ignore entries that are managed by vold.
989 options = pieces[4]
990 if "voldmanaged=" in options:
991 continue
992
993 # It's a good line, parse it.
994 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700995 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800996 options = options.split(",")
997 for i in options:
998 if i.startswith("length="):
999 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001000 elif i == "slotselect":
1001 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001002 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001003 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001004 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001005
Tao Baod1de6f32017-03-01 16:38:48 -08001006 mount_flags = pieces[3]
1007 # Honor the SELinux context if present.
1008 context = None
1009 for i in mount_flags.split(","):
1010 if i.startswith("context="):
1011 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001012
Tao Baod1de6f32017-03-01 16:38:48 -08001013 mount_point = pieces[1]
1014 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001015 device=pieces[0], length=length, context=context,
1016 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001017
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001018 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001019 # system. Other areas assume system is always at "/system" so point /system
1020 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001021 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001022 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001023 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001024 return d
1025
1026
Tao Bao765668f2019-10-04 22:03:00 -07001027def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1028 """Finds the path to recovery fstab and loads its contents."""
1029 # recovery fstab is only meaningful when installing an update via recovery
1030 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001031 if info_dict.get('ab_update') == 'true' and \
1032 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001033 return None
1034
1035 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1036 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1037 # cases, since it may load the info_dict from an old build (e.g. when
1038 # generating incremental OTAs from that build).
1039 system_root_image = info_dict.get('system_root_image') == 'true'
1040 if info_dict.get('no_recovery') != 'true':
1041 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1042 if isinstance(input_file, zipfile.ZipFile):
1043 if recovery_fstab_path not in input_file.namelist():
1044 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1045 else:
1046 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1047 if not os.path.exists(path):
1048 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1049 return LoadRecoveryFSTab(
1050 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1051 system_root_image)
1052
1053 if info_dict.get('recovery_as_boot') == 'true':
1054 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1055 if isinstance(input_file, zipfile.ZipFile):
1056 if recovery_fstab_path not in input_file.namelist():
1057 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1058 else:
1059 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1060 if not os.path.exists(path):
1061 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1062 return LoadRecoveryFSTab(
1063 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1064 system_root_image)
1065
1066 return None
1067
1068
Doug Zongker37974732010-09-16 17:44:38 -07001069def DumpInfoDict(d):
1070 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001071 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001072
Dan Albert8b72aef2015-03-23 19:13:21 -07001073
Daniel Norman55417142019-11-25 16:04:36 -08001074def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001075 """Merges dynamic partition info variables.
1076
1077 Args:
1078 framework_dict: The dictionary of dynamic partition info variables from the
1079 partial framework target files.
1080 vendor_dict: The dictionary of dynamic partition info variables from the
1081 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001082
1083 Returns:
1084 The merged dynamic partition info dictionary.
1085 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001086
1087 def uniq_concat(a, b):
1088 combined = set(a.split(" "))
1089 combined.update(set(b.split(" ")))
1090 combined = [item.strip() for item in combined if item.strip()]
1091 return " ".join(sorted(combined))
1092
1093 if (framework_dict.get("use_dynamic_partitions") !=
1094 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1095 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1096
1097 merged_dict = {"use_dynamic_partitions": "true"}
1098
1099 merged_dict["dynamic_partition_list"] = uniq_concat(
1100 framework_dict.get("dynamic_partition_list", ""),
1101 vendor_dict.get("dynamic_partition_list", ""))
1102
1103 # Super block devices are defined by the vendor dict.
1104 if "super_block_devices" in vendor_dict:
1105 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1106 for block_device in merged_dict["super_block_devices"].split(" "):
1107 key = "super_%s_device_size" % block_device
1108 if key not in vendor_dict:
1109 raise ValueError("Vendor dict does not contain required key %s." % key)
1110 merged_dict[key] = vendor_dict[key]
1111
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001112 # Partition groups and group sizes are defined by the vendor dict because
1113 # these values may vary for each board that uses a shared system image.
1114 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001115 for partition_group in merged_dict["super_partition_groups"].split(" "):
1116 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001117 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001118 if key not in vendor_dict:
1119 raise ValueError("Vendor dict does not contain required key %s." % key)
1120 merged_dict[key] = vendor_dict[key]
1121
1122 # Set the partition group's partition list using a concatenation of the
1123 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001124 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001125 merged_dict[key] = uniq_concat(
1126 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301127
Daniel Normanb0c75912020-09-24 14:30:21 -07001128 # Various other flags should be copied from the vendor dict, if defined.
1129 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1130 "super_metadata_device", "super_partition_error_limit",
1131 "super_partition_size"):
1132 if key in vendor_dict.keys():
1133 merged_dict[key] = vendor_dict[key]
1134
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001135 return merged_dict
1136
1137
Daniel Norman21c34f72020-11-11 17:25:50 -08001138def PartitionMapFromTargetFiles(target_files_dir):
1139 """Builds a map from partition -> path within an extracted target files directory."""
1140 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1141 possible_subdirs = {
1142 "system": ["SYSTEM"],
1143 "vendor": ["VENDOR", "SYSTEM/vendor"],
1144 "product": ["PRODUCT", "SYSTEM/product"],
1145 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1146 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1147 "vendor_dlkm": [
1148 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1149 ],
1150 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1151 }
1152 partition_map = {}
1153 for partition, subdirs in possible_subdirs.items():
1154 for subdir in subdirs:
1155 if os.path.exists(os.path.join(target_files_dir, subdir)):
1156 partition_map[partition] = subdir
1157 break
1158 return partition_map
1159
1160
Daniel Normand3351562020-10-29 12:33:11 -07001161def SharedUidPartitionViolations(uid_dict, partition_groups):
1162 """Checks for APK sharedUserIds that cross partition group boundaries.
1163
1164 This uses a single or merged build's shareduid_violation_modules.json
1165 output file, as generated by find_shareduid_violation.py or
1166 core/tasks/find-shareduid-violation.mk.
1167
1168 An error is defined as a sharedUserId that is found in a set of partitions
1169 that span more than one partition group.
1170
1171 Args:
1172 uid_dict: A dictionary created by using the standard json module to read a
1173 complete shareduid_violation_modules.json file.
1174 partition_groups: A list of groups, where each group is a list of
1175 partitions.
1176
1177 Returns:
1178 A list of error messages.
1179 """
1180 errors = []
1181 for uid, partitions in uid_dict.items():
1182 found_in_groups = [
1183 group for group in partition_groups
1184 if set(partitions.keys()) & set(group)
1185 ]
1186 if len(found_in_groups) > 1:
1187 errors.append(
1188 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1189 % (uid, ",".join(sorted(partitions.keys()))))
1190 return errors
1191
1192
Daniel Norman21c34f72020-11-11 17:25:50 -08001193def RunHostInitVerifier(product_out, partition_map):
1194 """Runs host_init_verifier on the init rc files within partitions.
1195
1196 host_init_verifier searches the etc/init path within each partition.
1197
1198 Args:
1199 product_out: PRODUCT_OUT directory, containing partition directories.
1200 partition_map: A map of partition name -> relative path within product_out.
1201 """
1202 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1203 cmd = ["host_init_verifier"]
1204 for partition, path in partition_map.items():
1205 if partition not in allowed_partitions:
1206 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1207 partition)
1208 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1209 # Add --property-contexts if the file exists on the partition.
1210 property_contexts = "%s_property_contexts" % (
1211 "plat" if partition == "system" else partition)
1212 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1213 property_contexts)
1214 if os.path.exists(property_contexts_path):
1215 cmd.append("--property-contexts=%s" % property_contexts_path)
1216 # Add the passwd file if the file exists on the partition.
1217 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1218 if os.path.exists(passwd_path):
1219 cmd.extend(["-p", passwd_path])
1220 return RunAndCheckOutput(cmd)
1221
1222
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001223def AppendAVBSigningArgs(cmd, partition):
1224 """Append signing arguments for avbtool."""
1225 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1226 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001227 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1228 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1229 if os.path.exists(new_key_path):
1230 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001231 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1232 if key_path and algorithm:
1233 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001234 avb_salt = OPTIONS.info_dict.get("avb_salt")
1235 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001236 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001237 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001238
1239
Tao Bao765668f2019-10-04 22:03:00 -07001240def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001241 """Returns the VBMeta arguments for partition.
1242
1243 It sets up the VBMeta argument by including the partition descriptor from the
1244 given 'image', or by configuring the partition as a chained partition.
1245
1246 Args:
1247 partition: The name of the partition (e.g. "system").
1248 image: The path to the partition image.
1249 info_dict: A dict returned by common.LoadInfoDict(). Will use
1250 OPTIONS.info_dict if None has been given.
1251
1252 Returns:
1253 A list of VBMeta arguments.
1254 """
1255 if info_dict is None:
1256 info_dict = OPTIONS.info_dict
1257
1258 # Check if chain partition is used.
1259 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001260 if not key_path:
1261 return ["--include_descriptors_from_image", image]
1262
1263 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1264 # into vbmeta.img. The recovery image will be configured on an independent
1265 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1266 # See details at
1267 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001268 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001269 return []
1270
1271 # Otherwise chain the partition into vbmeta.
1272 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1273 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001274
1275
Tao Bao02a08592018-07-22 12:40:45 -07001276def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1277 """Constructs and returns the arg to build or verify a chained partition.
1278
1279 Args:
1280 partition: The partition name.
1281 info_dict: The info dict to look up the key info and rollback index
1282 location.
1283 key: The key to be used for building or verifying the partition. Defaults to
1284 the key listed in info_dict.
1285
1286 Returns:
1287 A string of form "partition:rollback_index_location:key" that can be used to
1288 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001289 """
1290 if key is None:
1291 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001292 if key and not os.path.exists(key) and OPTIONS.search_path:
1293 new_key_path = os.path.join(OPTIONS.search_path, key)
1294 if os.path.exists(new_key_path):
1295 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001296 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001297 rollback_index_location = info_dict[
1298 "avb_" + partition + "_rollback_index_location"]
1299 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1300
1301
Tianjie20dd8f22020-04-19 15:51:16 -07001302def ConstructAftlMakeImageCommands(output_image):
1303 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001304
1305 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001306 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001307 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1308 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1309 'No AFTL manufacturer key provided.'
1310
1311 vbmeta_image = MakeTempFile()
1312 os.rename(output_image, vbmeta_image)
1313 build_info = BuildInfo(OPTIONS.info_dict)
1314 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001315 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001316 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001317 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001318 "--vbmeta_image_path", vbmeta_image,
1319 "--output", output_image,
1320 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001321 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001322 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1323 "--algorithm", "SHA256_RSA4096",
1324 "--padding", "4096"]
1325 if OPTIONS.aftl_signer_helper:
1326 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001327 return aftl_cmd
1328
1329
1330def AddAftlInclusionProof(output_image):
1331 """Appends the aftl inclusion proof to the vbmeta image."""
1332
1333 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001334 RunAndCheckOutput(aftl_cmd)
1335
1336 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1337 output_image, '--transparency_log_pub_keys',
1338 OPTIONS.aftl_key_path]
1339 RunAndCheckOutput(verify_cmd)
1340
1341
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001342def AppendGkiSigningArgs(cmd):
1343 """Append GKI signing arguments for mkbootimg."""
1344 # e.g., --gki_signing_key path/to/signing_key
1345 # --gki_signing_algorithm SHA256_RSA4096"
1346
1347 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1348 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1349 if not key_path:
1350 return
1351
1352 if not os.path.exists(key_path) and OPTIONS.search_path:
1353 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1354 if os.path.exists(new_key_path):
1355 key_path = new_key_path
1356
1357 # Checks key_path exists, before appending --gki_signing_* args.
1358 if not os.path.exists(key_path):
1359 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1360
1361 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1362 if key_path and algorithm:
1363 cmd.extend(["--gki_signing_key", key_path,
1364 "--gki_signing_algorithm", algorithm])
1365
1366 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1367 if signature_args:
1368 cmd.extend(["--gki_signing_signature_args", signature_args])
1369
1370
Daniel Norman276f0622019-07-26 14:13:51 -07001371def BuildVBMeta(image_path, partitions, name, needed_partitions):
1372 """Creates a VBMeta image.
1373
1374 It generates the requested VBMeta image. The requested image could be for
1375 top-level or chained VBMeta image, which is determined based on the name.
1376
1377 Args:
1378 image_path: The output path for the new VBMeta image.
1379 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001380 values. Only valid partition names are accepted, as partitions listed
1381 in common.AVB_PARTITIONS and custom partitions listed in
1382 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001383 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1384 needed_partitions: Partitions whose descriptors should be included into the
1385 generated VBMeta image.
1386
1387 Raises:
1388 AssertionError: On invalid input args.
1389 """
1390 avbtool = OPTIONS.info_dict["avb_avbtool"]
1391 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1392 AppendAVBSigningArgs(cmd, name)
1393
Hongguang Chenf23364d2020-04-27 18:36:36 -07001394 custom_partitions = OPTIONS.info_dict.get(
1395 "avb_custom_images_partition_list", "").strip().split()
1396
Daniel Norman276f0622019-07-26 14:13:51 -07001397 for partition, path in partitions.items():
1398 if partition not in needed_partitions:
1399 continue
1400 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001401 partition in AVB_VBMETA_PARTITIONS or
1402 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001403 'Unknown partition: {}'.format(partition)
1404 assert os.path.exists(path), \
1405 'Failed to find {} for {}'.format(path, partition)
1406 cmd.extend(GetAvbPartitionArg(partition, path))
1407
1408 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1409 if args and args.strip():
1410 split_args = shlex.split(args)
1411 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001412 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001413 # as a path relative to source tree, which may not be available at the
1414 # same location when running this script (we have the input target_files
1415 # zip only). For such cases, we additionally scan other locations (e.g.
1416 # IMAGES/, RADIO/, etc) before bailing out.
1417 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001418 chained_image = split_args[index + 1]
1419 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001420 continue
1421 found = False
1422 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1423 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001424 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001425 if os.path.exists(alt_path):
1426 split_args[index + 1] = alt_path
1427 found = True
1428 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001429 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001430 cmd.extend(split_args)
1431
1432 RunAndCheckOutput(cmd)
1433
Tianjie Xueaed60c2020-03-12 00:33:28 -07001434 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001435 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001436 AddAftlInclusionProof(image_path)
1437
Daniel Norman276f0622019-07-26 14:13:51 -07001438
J. Avila98cd4cc2020-06-10 20:09:10 +00001439def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001440 ramdisk_img = tempfile.NamedTemporaryFile()
1441
1442 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1443 cmd = ["mkbootfs", "-f", fs_config_file,
1444 os.path.join(sourcedir, "RAMDISK")]
1445 else:
1446 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1447 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001448 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001449 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001450 stdout=ramdisk_img.file.fileno())
1451 else:
1452 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001453
1454 p2.wait()
1455 p1.wait()
1456 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001457 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001458
1459 return ramdisk_img
1460
1461
Steve Muckle9793cf62020-04-08 18:27:00 -07001462def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001463 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001464 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001465
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001466 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001467 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1468 we are building a two-step special image (i.e. building a recovery image to
1469 be loaded into /boot in two-step OTAs).
1470
1471 Return the image data, or None if sourcedir does not appear to contains files
1472 for building the requested image.
1473 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001474
Yifan Hong63c5ca12020-10-08 11:54:02 -07001475 if info_dict is None:
1476 info_dict = OPTIONS.info_dict
1477
Steve Muckle9793cf62020-04-08 18:27:00 -07001478 # "boot" or "recovery", without extension.
1479 partition_name = os.path.basename(sourcedir).lower()
1480
Yifan Hong63c5ca12020-10-08 11:54:02 -07001481 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001482 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001483 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1484 logger.info("Excluded kernel binary from recovery image.")
1485 else:
1486 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001487 else:
1488 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001489 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001490 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001491 return None
1492
1493 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001494 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001495
Doug Zongkereef39442009-04-02 12:14:19 -07001496 img = tempfile.NamedTemporaryFile()
1497
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001498 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001499 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1500 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001501
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001502 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1503 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1504
Yifan Hong63c5ca12020-10-08 11:54:02 -07001505 cmd = [mkbootimg]
1506 if kernel:
1507 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001508
Benoit Fradina45a8682014-07-14 21:00:43 +02001509 fn = os.path.join(sourcedir, "second")
1510 if os.access(fn, os.F_OK):
1511 cmd.append("--second")
1512 cmd.append(fn)
1513
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001514 fn = os.path.join(sourcedir, "dtb")
1515 if os.access(fn, os.F_OK):
1516 cmd.append("--dtb")
1517 cmd.append(fn)
1518
Doug Zongker171f1cd2009-06-15 22:36:37 -07001519 fn = os.path.join(sourcedir, "cmdline")
1520 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001521 cmd.append("--cmdline")
1522 cmd.append(open(fn).read().rstrip("\n"))
1523
1524 fn = os.path.join(sourcedir, "base")
1525 if os.access(fn, os.F_OK):
1526 cmd.append("--base")
1527 cmd.append(open(fn).read().rstrip("\n"))
1528
Ying Wang4de6b5b2010-08-25 14:29:34 -07001529 fn = os.path.join(sourcedir, "pagesize")
1530 if os.access(fn, os.F_OK):
1531 cmd.append("--pagesize")
1532 cmd.append(open(fn).read().rstrip("\n"))
1533
Steve Mucklef84668e2020-03-16 19:13:46 -07001534 if partition_name == "recovery":
1535 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301536 if not args:
1537 # Fall back to "mkbootimg_args" for recovery image
1538 # in case "recovery_mkbootimg_args" is not set.
1539 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001540 else:
1541 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001542 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001543 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001544
Tao Bao76def242017-11-21 09:25:31 -08001545 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001546 if args and args.strip():
1547 cmd.extend(shlex.split(args))
1548
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001549 if has_ramdisk:
1550 cmd.extend(["--ramdisk", ramdisk_img.name])
1551
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001552 AppendGkiSigningArgs(cmd)
1553
Tao Baod95e9fd2015-03-29 23:07:41 -07001554 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001555 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001556 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001557 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001558 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001559 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001560
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001561 if partition_name == "recovery":
1562 if info_dict.get("include_recovery_dtbo") == "true":
1563 fn = os.path.join(sourcedir, "recovery_dtbo")
1564 cmd.extend(["--recovery_dtbo", fn])
1565 if info_dict.get("include_recovery_acpio") == "true":
1566 fn = os.path.join(sourcedir, "recovery_acpio")
1567 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001568
Tao Bao986ee862018-10-04 15:46:16 -07001569 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001570
Tao Bao76def242017-11-21 09:25:31 -08001571 if (info_dict.get("boot_signer") == "true" and
1572 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001573 # Hard-code the path as "/boot" for two-step special recovery image (which
1574 # will be loaded into /boot during the two-step OTA).
1575 if two_step_image:
1576 path = "/boot"
1577 else:
Tao Baobf70c312017-07-11 17:27:55 -07001578 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001579 cmd = [OPTIONS.boot_signer_path]
1580 cmd.extend(OPTIONS.boot_signer_args)
1581 cmd.extend([path, img.name,
1582 info_dict["verity_key"] + ".pk8",
1583 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001584 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001585
Tao Baod95e9fd2015-03-29 23:07:41 -07001586 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001587 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001588 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001589 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001590 # We have switched from the prebuilt futility binary to using the tool
1591 # (futility-host) built from the source. Override the setting in the old
1592 # TF.zip.
1593 futility = info_dict["futility"]
1594 if futility.startswith("prebuilts/"):
1595 futility = "futility-host"
1596 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001597 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001598 info_dict["vboot_key"] + ".vbprivk",
1599 info_dict["vboot_subkey"] + ".vbprivk",
1600 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001601 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001602 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001603
Tao Baof3282b42015-04-01 11:21:55 -07001604 # Clean up the temp files.
1605 img_unsigned.close()
1606 img_keyblock.close()
1607
David Zeuthen8fecb282017-12-01 16:24:01 -05001608 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001609 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001610 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001611 if partition_name == "recovery":
1612 part_size = info_dict["recovery_size"]
1613 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001614 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001615 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001616 "--partition_size", str(part_size), "--partition_name",
1617 partition_name]
1618 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001619 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001620 if args and args.strip():
1621 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001622 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001623
1624 img.seek(os.SEEK_SET, 0)
1625 data = img.read()
1626
1627 if has_ramdisk:
1628 ramdisk_img.close()
1629 img.close()
1630
1631 return data
1632
1633
Doug Zongkerd5131602012-08-02 14:46:42 -07001634def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001635 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001636 """Return a File object with the desired bootable image.
1637
1638 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1639 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1640 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001641
Doug Zongker55d93282011-01-25 17:03:34 -08001642 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1643 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001644 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001645 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001646
1647 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1648 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001649 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001650 return File.FromLocalFile(name, prebuilt_path)
1651
Tao Bao32fcdab2018-10-12 10:30:39 -07001652 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001653
1654 if info_dict is None:
1655 info_dict = OPTIONS.info_dict
1656
1657 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001658 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1659 # for recovery.
1660 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1661 prebuilt_name != "boot.img" or
1662 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001663
Doug Zongker6f1d0312014-08-22 08:07:12 -07001664 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001665 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001666 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001667 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001668 if data:
1669 return File(name, data)
1670 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001671
Doug Zongkereef39442009-04-02 12:14:19 -07001672
Steve Mucklee1b10862019-07-10 10:49:37 -07001673def _BuildVendorBootImage(sourcedir, info_dict=None):
1674 """Build a vendor boot image from the specified sourcedir.
1675
1676 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1677 turn them into a vendor boot image.
1678
1679 Return the image data, or None if sourcedir does not appear to contains files
1680 for building the requested image.
1681 """
1682
1683 if info_dict is None:
1684 info_dict = OPTIONS.info_dict
1685
1686 img = tempfile.NamedTemporaryFile()
1687
J. Avila98cd4cc2020-06-10 20:09:10 +00001688 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1689 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001690
1691 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1692 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1693
1694 cmd = [mkbootimg]
1695
1696 fn = os.path.join(sourcedir, "dtb")
1697 if os.access(fn, os.F_OK):
1698 cmd.append("--dtb")
1699 cmd.append(fn)
1700
1701 fn = os.path.join(sourcedir, "vendor_cmdline")
1702 if os.access(fn, os.F_OK):
1703 cmd.append("--vendor_cmdline")
1704 cmd.append(open(fn).read().rstrip("\n"))
1705
1706 fn = os.path.join(sourcedir, "base")
1707 if os.access(fn, os.F_OK):
1708 cmd.append("--base")
1709 cmd.append(open(fn).read().rstrip("\n"))
1710
1711 fn = os.path.join(sourcedir, "pagesize")
1712 if os.access(fn, os.F_OK):
1713 cmd.append("--pagesize")
1714 cmd.append(open(fn).read().rstrip("\n"))
1715
1716 args = info_dict.get("mkbootimg_args")
1717 if args and args.strip():
1718 cmd.extend(shlex.split(args))
1719
1720 args = info_dict.get("mkbootimg_version_args")
1721 if args and args.strip():
1722 cmd.extend(shlex.split(args))
1723
1724 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1725 cmd.extend(["--vendor_boot", img.name])
1726
Devin Moore50509012021-01-13 10:45:04 -08001727 fn = os.path.join(sourcedir, "vendor_bootconfig")
1728 if os.access(fn, os.F_OK):
1729 cmd.append("--vendor_bootconfig")
1730 cmd.append(fn)
1731
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001732 ramdisk_fragment_imgs = []
1733 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1734 if os.access(fn, os.F_OK):
1735 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1736 for ramdisk_fragment in ramdisk_fragments:
1737 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1738 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1739 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1740 # Use prebuilt image if found, else create ramdisk from supplied files.
1741 if os.access(fn, os.F_OK):
1742 ramdisk_fragment_pathname = fn
1743 else:
1744 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1745 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1746 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1747 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1748 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1749
Steve Mucklee1b10862019-07-10 10:49:37 -07001750 RunAndCheckOutput(cmd)
1751
1752 # AVB: if enabled, calculate and add hash.
1753 if info_dict.get("avb_enable") == "true":
1754 avbtool = info_dict["avb_avbtool"]
1755 part_size = info_dict["vendor_boot_size"]
1756 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001757 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001758 AppendAVBSigningArgs(cmd, "vendor_boot")
1759 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1760 if args and args.strip():
1761 cmd.extend(shlex.split(args))
1762 RunAndCheckOutput(cmd)
1763
1764 img.seek(os.SEEK_SET, 0)
1765 data = img.read()
1766
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001767 for f in ramdisk_fragment_imgs:
1768 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001769 ramdisk_img.close()
1770 img.close()
1771
1772 return data
1773
1774
1775def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1776 info_dict=None):
1777 """Return a File object with the desired vendor boot image.
1778
1779 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1780 the source files in 'unpack_dir'/'tree_subdir'."""
1781
1782 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1783 if os.path.exists(prebuilt_path):
1784 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1785 return File.FromLocalFile(name, prebuilt_path)
1786
1787 logger.info("building image from target_files %s...", tree_subdir)
1788
1789 if info_dict is None:
1790 info_dict = OPTIONS.info_dict
1791
Kelvin Zhang0876c412020-06-23 15:06:58 -04001792 data = _BuildVendorBootImage(
1793 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001794 if data:
1795 return File(name, data)
1796 return None
1797
1798
Narayan Kamatha07bf042017-08-14 14:49:21 +01001799def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001800 """Gunzips the given gzip compressed file to a given output file."""
1801 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001802 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001803 shutil.copyfileobj(in_file, out_file)
1804
1805
Tao Bao0ff15de2019-03-20 11:26:06 -07001806def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001807 """Unzips the archive to the given directory.
1808
1809 Args:
1810 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001811 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001812 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1813 archvie. Non-matching patterns will be filtered out. If there's no match
1814 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001815 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001816 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001817 if patterns is not None:
1818 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001819 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001820 names = input_zip.namelist()
1821 filtered = [
1822 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1823
1824 # There isn't any matching files. Don't unzip anything.
1825 if not filtered:
1826 return
1827 cmd.extend(filtered)
1828
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001829 RunAndCheckOutput(cmd)
1830
1831
Doug Zongker75f17362009-12-08 13:46:44 -08001832def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001833 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001834
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001835 Args:
1836 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1837 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1838
1839 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1840 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001841
Tao Bao1c830bf2017-12-25 10:43:47 -08001842 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001843 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001844 """
Doug Zongkereef39442009-04-02 12:14:19 -07001845
Tao Bao1c830bf2017-12-25 10:43:47 -08001846 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001847 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1848 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001849 UnzipToDir(m.group(1), tmp, pattern)
1850 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001851 filename = m.group(1)
1852 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001853 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001854
Tao Baodba59ee2018-01-09 13:21:02 -08001855 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001856
1857
Yifan Hong8a66a712019-04-04 15:37:57 -07001858def GetUserImage(which, tmpdir, input_zip,
1859 info_dict=None,
1860 allow_shared_blocks=None,
1861 hashtree_info_generator=None,
1862 reset_file_map=False):
1863 """Returns an Image object suitable for passing to BlockImageDiff.
1864
1865 This function loads the specified image from the given path. If the specified
1866 image is sparse, it also performs additional processing for OTA purpose. For
1867 example, it always adds block 0 to clobbered blocks list. It also detects
1868 files that cannot be reconstructed from the block list, for whom we should
1869 avoid applying imgdiff.
1870
1871 Args:
1872 which: The partition name.
1873 tmpdir: The directory that contains the prebuilt image and block map file.
1874 input_zip: The target-files ZIP archive.
1875 info_dict: The dict to be looked up for relevant info.
1876 allow_shared_blocks: If image is sparse, whether having shared blocks is
1877 allowed. If none, it is looked up from info_dict.
1878 hashtree_info_generator: If present and image is sparse, generates the
1879 hashtree_info for this sparse image.
1880 reset_file_map: If true and image is sparse, reset file map before returning
1881 the image.
1882 Returns:
1883 A Image object. If it is a sparse image and reset_file_map is False, the
1884 image will have file_map info loaded.
1885 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001886 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001887 info_dict = LoadInfoDict(input_zip)
1888
1889 is_sparse = info_dict.get("extfs_sparse_flag")
1890
1891 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1892 # shared blocks (i.e. some blocks will show up in multiple files' block
1893 # list). We can only allocate such shared blocks to the first "owner", and
1894 # disable imgdiff for all later occurrences.
1895 if allow_shared_blocks is None:
1896 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1897
1898 if is_sparse:
1899 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1900 hashtree_info_generator)
1901 if reset_file_map:
1902 img.ResetFileMap()
1903 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001904 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001905
1906
1907def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1908 """Returns a Image object suitable for passing to BlockImageDiff.
1909
1910 This function loads the specified non-sparse image from the given path.
1911
1912 Args:
1913 which: The partition name.
1914 tmpdir: The directory that contains the prebuilt image and block map file.
1915 Returns:
1916 A Image object.
1917 """
1918 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1919 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1920
1921 # The image and map files must have been created prior to calling
1922 # ota_from_target_files.py (since LMP).
1923 assert os.path.exists(path) and os.path.exists(mappath)
1924
Tianjie Xu41976c72019-07-03 13:57:01 -07001925 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1926
Yifan Hong8a66a712019-04-04 15:37:57 -07001927
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001928def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1929 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001930 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1931
1932 This function loads the specified sparse image from the given path, and
1933 performs additional processing for OTA purpose. For example, it always adds
1934 block 0 to clobbered blocks list. It also detects files that cannot be
1935 reconstructed from the block list, for whom we should avoid applying imgdiff.
1936
1937 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001938 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001939 tmpdir: The directory that contains the prebuilt image and block map file.
1940 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001941 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001942 hashtree_info_generator: If present, generates the hashtree_info for this
1943 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001944 Returns:
1945 A SparseImage object, with file_map info loaded.
1946 """
Tao Baoc765cca2018-01-31 17:32:40 -08001947 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1948 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1949
1950 # The image and map files must have been created prior to calling
1951 # ota_from_target_files.py (since LMP).
1952 assert os.path.exists(path) and os.path.exists(mappath)
1953
1954 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1955 # it to clobbered_blocks so that it will be written to the target
1956 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1957 clobbered_blocks = "0"
1958
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001959 image = sparse_img.SparseImage(
1960 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1961 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001962
1963 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1964 # if they contain all zeros. We can't reconstruct such a file from its block
1965 # list. Tag such entries accordingly. (Bug: 65213616)
1966 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001967 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001968 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001969 continue
1970
Tom Cherryd14b8952018-08-09 14:26:00 -07001971 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1972 # filename listed in system.map may contain an additional leading slash
1973 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1974 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001975 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001976 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001977 arcname = entry.lstrip('/')
1978 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001979 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001980 else:
1981 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07001982
1983 assert arcname in input_zip.namelist(), \
1984 "Failed to find the ZIP entry for {}".format(entry)
1985
Tao Baoc765cca2018-01-31 17:32:40 -08001986 info = input_zip.getinfo(arcname)
1987 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001988
1989 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001990 # image, check the original block list to determine its completeness. Note
1991 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001992 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001993 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001994
Tao Baoc765cca2018-01-31 17:32:40 -08001995 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1996 ranges.extra['incomplete'] = True
1997
1998 return image
1999
2000
Doug Zongkereef39442009-04-02 12:14:19 -07002001def GetKeyPasswords(keylist):
2002 """Given a list of keys, prompt the user to enter passwords for
2003 those which require them. Return a {key: password} dict. password
2004 will be None if the key has no password."""
2005
Doug Zongker8ce7c252009-05-22 13:34:54 -07002006 no_passwords = []
2007 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002008 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002009 devnull = open("/dev/null", "w+b")
2010 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002011 # We don't need a password for things that aren't really keys.
2012 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002013 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002014 continue
2015
T.R. Fullhart37e10522013-03-18 10:31:26 -07002016 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002017 "-inform", "DER", "-nocrypt"],
2018 stdin=devnull.fileno(),
2019 stdout=devnull.fileno(),
2020 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002021 p.communicate()
2022 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002023 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002024 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002025 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002026 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2027 "-inform", "DER", "-passin", "pass:"],
2028 stdin=devnull.fileno(),
2029 stdout=devnull.fileno(),
2030 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002031 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002032 if p.returncode == 0:
2033 # Encrypted key with empty string as password.
2034 key_passwords[k] = ''
2035 elif stderr.startswith('Error decrypting key'):
2036 # Definitely encrypted key.
2037 # It would have said "Error reading key" if it didn't parse correctly.
2038 need_passwords.append(k)
2039 else:
2040 # Potentially, a type of key that openssl doesn't understand.
2041 # We'll let the routines in signapk.jar handle it.
2042 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002043 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002044
T.R. Fullhart37e10522013-03-18 10:31:26 -07002045 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002046 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002047 return key_passwords
2048
2049
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002050def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002051 """Gets the minSdkVersion declared in the APK.
2052
changho.shin0f125362019-07-08 10:59:00 +09002053 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002054 This can be both a decimal number (API Level) or a codename.
2055
2056 Args:
2057 apk_name: The APK filename.
2058
2059 Returns:
2060 The parsed SDK version string.
2061
2062 Raises:
2063 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002064 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002065 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002066 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002067 stderr=subprocess.PIPE)
2068 stdoutdata, stderrdata = proc.communicate()
2069 if proc.returncode != 0:
2070 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002071 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002072 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002073
Tao Baof47bf0f2018-03-21 23:28:51 -07002074 for line in stdoutdata.split("\n"):
2075 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002076 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2077 if m:
2078 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002079 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002080
2081
2082def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002083 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002084
Tao Baof47bf0f2018-03-21 23:28:51 -07002085 If minSdkVersion is set to a codename, it is translated to a number using the
2086 provided map.
2087
2088 Args:
2089 apk_name: The APK filename.
2090
2091 Returns:
2092 The parsed SDK version number.
2093
2094 Raises:
2095 ExternalError: On failing to get the min SDK version number.
2096 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002097 version = GetMinSdkVersion(apk_name)
2098 try:
2099 return int(version)
2100 except ValueError:
2101 # Not a decimal number. Codename?
2102 if version in codename_to_api_level_map:
2103 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002104 raise ExternalError(
2105 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2106 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002107
2108
2109def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002110 codename_to_api_level_map=None, whole_file=False,
2111 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002112 """Sign the input_name zip/jar/apk, producing output_name. Use the
2113 given key and password (the latter may be None if the key does not
2114 have a password.
2115
Doug Zongker951495f2009-08-14 12:44:19 -07002116 If whole_file is true, use the "-w" option to SignApk to embed a
2117 signature that covers the whole file in the archive comment of the
2118 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002119
2120 min_api_level is the API Level (int) of the oldest platform this file may end
2121 up on. If not specified for an APK, the API Level is obtained by interpreting
2122 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2123
2124 codename_to_api_level_map is needed to translate the codename which may be
2125 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002126
2127 Caller may optionally specify extra args to be passed to SignApk, which
2128 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002129 """
Tao Bao76def242017-11-21 09:25:31 -08002130 if codename_to_api_level_map is None:
2131 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002132 if extra_signapk_args is None:
2133 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002134
Alex Klyubin9667b182015-12-10 13:38:50 -08002135 java_library_path = os.path.join(
2136 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2137
Tao Baoe95540e2016-11-08 12:08:53 -08002138 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2139 ["-Djava.library.path=" + java_library_path,
2140 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002141 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002142 if whole_file:
2143 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002144
2145 min_sdk_version = min_api_level
2146 if min_sdk_version is None:
2147 if not whole_file:
2148 min_sdk_version = GetMinSdkVersionInt(
2149 input_name, codename_to_api_level_map)
2150 if min_sdk_version is not None:
2151 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2152
T.R. Fullhart37e10522013-03-18 10:31:26 -07002153 cmd.extend([key + OPTIONS.public_key_suffix,
2154 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002155 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002156
Tao Bao73dd4f42018-10-04 16:25:33 -07002157 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002158 if password is not None:
2159 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002160 stdoutdata, _ = proc.communicate(password)
2161 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002162 raise ExternalError(
2163 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002164 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002165
Doug Zongkereef39442009-04-02 12:14:19 -07002166
Doug Zongker37974732010-09-16 17:44:38 -07002167def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002168 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002169
Tao Bao9dd909e2017-11-14 11:27:32 -08002170 For non-AVB images, raise exception if the data is too big. Print a warning
2171 if the data is nearing the maximum size.
2172
2173 For AVB images, the actual image size should be identical to the limit.
2174
2175 Args:
2176 data: A string that contains all the data for the partition.
2177 target: The partition name. The ".img" suffix is optional.
2178 info_dict: The dict to be looked up for relevant info.
2179 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002180 if target.endswith(".img"):
2181 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002182 mount_point = "/" + target
2183
Ying Wangf8824af2014-06-03 14:07:27 -07002184 fs_type = None
2185 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002186 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002187 if mount_point == "/userdata":
2188 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002189 p = info_dict["fstab"][mount_point]
2190 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002191 device = p.device
2192 if "/" in device:
2193 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002194 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002195 if not fs_type or not limit:
2196 return
Doug Zongkereef39442009-04-02 12:14:19 -07002197
Andrew Boie0f9aec82012-02-14 09:32:52 -08002198 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002199 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2200 # path.
2201 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2202 if size != limit:
2203 raise ExternalError(
2204 "Mismatching image size for %s: expected %d actual %d" % (
2205 target, limit, size))
2206 else:
2207 pct = float(size) * 100.0 / limit
2208 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2209 if pct >= 99.0:
2210 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002211
2212 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002213 logger.warning("\n WARNING: %s\n", msg)
2214 else:
2215 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002216
2217
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002218def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002219 """Parses the APK certs info from a given target-files zip.
2220
2221 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2222 tuple with the following elements: (1) a dictionary that maps packages to
2223 certs (based on the "certificate" and "private_key" attributes in the file;
2224 (2) a string representing the extension of compressed APKs in the target files
2225 (e.g ".gz", ".bro").
2226
2227 Args:
2228 tf_zip: The input target_files ZipFile (already open).
2229
2230 Returns:
2231 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2232 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2233 no compressed APKs.
2234 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002235 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002236 compressed_extension = None
2237
Tao Bao0f990332017-09-08 19:02:54 -07002238 # META/apkcerts.txt contains the info for _all_ the packages known at build
2239 # time. Filter out the ones that are not installed.
2240 installed_files = set()
2241 for name in tf_zip.namelist():
2242 basename = os.path.basename(name)
2243 if basename:
2244 installed_files.add(basename)
2245
Tao Baoda30cfa2017-12-01 16:19:46 -08002246 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002247 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002248 if not line:
2249 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002250 m = re.match(
2251 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002252 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2253 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002254 line)
2255 if not m:
2256 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002257
Tao Bao818ddf52018-01-05 11:17:34 -08002258 matches = m.groupdict()
2259 cert = matches["CERT"]
2260 privkey = matches["PRIVKEY"]
2261 name = matches["NAME"]
2262 this_compressed_extension = matches["COMPRESSED"]
2263
2264 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2265 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2266 if cert in SPECIAL_CERT_STRINGS and not privkey:
2267 certmap[name] = cert
2268 elif (cert.endswith(OPTIONS.public_key_suffix) and
2269 privkey.endswith(OPTIONS.private_key_suffix) and
2270 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2271 certmap[name] = cert[:-public_key_suffix_len]
2272 else:
2273 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2274
2275 if not this_compressed_extension:
2276 continue
2277
2278 # Only count the installed files.
2279 filename = name + '.' + this_compressed_extension
2280 if filename not in installed_files:
2281 continue
2282
2283 # Make sure that all the values in the compression map have the same
2284 # extension. We don't support multiple compression methods in the same
2285 # system image.
2286 if compressed_extension:
2287 if this_compressed_extension != compressed_extension:
2288 raise ValueError(
2289 "Multiple compressed extensions: {} vs {}".format(
2290 compressed_extension, this_compressed_extension))
2291 else:
2292 compressed_extension = this_compressed_extension
2293
2294 return (certmap,
2295 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002296
2297
Doug Zongkereef39442009-04-02 12:14:19 -07002298COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002299Global options
2300
2301 -p (--path) <dir>
2302 Prepend <dir>/bin to the list of places to search for binaries run by this
2303 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002304
Doug Zongker05d3dea2009-06-22 11:32:31 -07002305 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002306 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002307
Tao Bao30df8b42018-04-23 15:32:53 -07002308 -x (--extra) <key=value>
2309 Add a key/value pair to the 'extras' dict, which device-specific extension
2310 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002311
Doug Zongkereef39442009-04-02 12:14:19 -07002312 -v (--verbose)
2313 Show command lines being executed.
2314
2315 -h (--help)
2316 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002317
2318 --logfile <file>
2319 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002320"""
2321
Kelvin Zhang0876c412020-06-23 15:06:58 -04002322
Doug Zongkereef39442009-04-02 12:14:19 -07002323def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002324 print(docstring.rstrip("\n"))
2325 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002326
2327
2328def ParseOptions(argv,
2329 docstring,
2330 extra_opts="", extra_long_opts=(),
2331 extra_option_handler=None):
2332 """Parse the options in argv and return any arguments that aren't
2333 flags. docstring is the calling module's docstring, to be displayed
2334 for errors and -h. extra_opts and extra_long_opts are for flags
2335 defined by the caller, which are processed by passing them to
2336 extra_option_handler."""
2337
2338 try:
2339 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002340 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002341 ["help", "verbose", "path=", "signapk_path=",
2342 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002343 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002344 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2345 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002346 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2347 "aftl_key_path=", "aftl_manufacturer_key_path=",
2348 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002349 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002350 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002351 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002352 sys.exit(2)
2353
Doug Zongkereef39442009-04-02 12:14:19 -07002354 for o, a in opts:
2355 if o in ("-h", "--help"):
2356 Usage(docstring)
2357 sys.exit()
2358 elif o in ("-v", "--verbose"):
2359 OPTIONS.verbose = True
2360 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002361 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002362 elif o in ("--signapk_path",):
2363 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002364 elif o in ("--signapk_shared_library_path",):
2365 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002366 elif o in ("--extra_signapk_args",):
2367 OPTIONS.extra_signapk_args = shlex.split(a)
2368 elif o in ("--java_path",):
2369 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002370 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002371 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002372 elif o in ("--android_jar_path",):
2373 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002374 elif o in ("--public_key_suffix",):
2375 OPTIONS.public_key_suffix = a
2376 elif o in ("--private_key_suffix",):
2377 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002378 elif o in ("--boot_signer_path",):
2379 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002380 elif o in ("--boot_signer_args",):
2381 OPTIONS.boot_signer_args = shlex.split(a)
2382 elif o in ("--verity_signer_path",):
2383 OPTIONS.verity_signer_path = a
2384 elif o in ("--verity_signer_args",):
2385 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002386 elif o in ("--aftl_tool_path",):
2387 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002388 elif o in ("--aftl_server",):
2389 OPTIONS.aftl_server = a
2390 elif o in ("--aftl_key_path",):
2391 OPTIONS.aftl_key_path = a
2392 elif o in ("--aftl_manufacturer_key_path",):
2393 OPTIONS.aftl_manufacturer_key_path = a
2394 elif o in ("--aftl_signer_helper",):
2395 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002396 elif o in ("-s", "--device_specific"):
2397 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002398 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002399 key, value = a.split("=", 1)
2400 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002401 elif o in ("--logfile",):
2402 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002403 else:
2404 if extra_option_handler is None or not extra_option_handler(o, a):
2405 assert False, "unknown option \"%s\"" % (o,)
2406
Doug Zongker85448772014-09-09 14:59:20 -07002407 if OPTIONS.search_path:
2408 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2409 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002410
2411 return args
2412
2413
Tao Bao4c851b12016-09-19 13:54:38 -07002414def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002415 """Make a temp file and add it to the list of things to be deleted
2416 when Cleanup() is called. Return the filename."""
2417 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2418 os.close(fd)
2419 OPTIONS.tempfiles.append(fn)
2420 return fn
2421
2422
Tao Bao1c830bf2017-12-25 10:43:47 -08002423def MakeTempDir(prefix='tmp', suffix=''):
2424 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2425
2426 Returns:
2427 The absolute pathname of the new directory.
2428 """
2429 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2430 OPTIONS.tempfiles.append(dir_name)
2431 return dir_name
2432
2433
Doug Zongkereef39442009-04-02 12:14:19 -07002434def Cleanup():
2435 for i in OPTIONS.tempfiles:
2436 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002437 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002438 else:
2439 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002440 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002441
2442
2443class PasswordManager(object):
2444 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002445 self.editor = os.getenv("EDITOR")
2446 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002447
2448 def GetPasswords(self, items):
2449 """Get passwords corresponding to each string in 'items',
2450 returning a dict. (The dict may have keys in addition to the
2451 values in 'items'.)
2452
2453 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2454 user edit that file to add more needed passwords. If no editor is
2455 available, or $ANDROID_PW_FILE isn't define, prompts the user
2456 interactively in the ordinary way.
2457 """
2458
2459 current = self.ReadFile()
2460
2461 first = True
2462 while True:
2463 missing = []
2464 for i in items:
2465 if i not in current or not current[i]:
2466 missing.append(i)
2467 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002468 if not missing:
2469 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002470
2471 for i in missing:
2472 current[i] = ""
2473
2474 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002475 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002476 if sys.version_info[0] >= 3:
2477 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002478 answer = raw_input("try to edit again? [y]> ").strip()
2479 if answer and answer[0] not in 'yY':
2480 raise RuntimeError("key passwords unavailable")
2481 first = False
2482
2483 current = self.UpdateAndReadFile(current)
2484
Kelvin Zhang0876c412020-06-23 15:06:58 -04002485 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002486 """Prompt the user to enter a value (password) for each key in
2487 'current' whose value is fales. Returns a new dict with all the
2488 values.
2489 """
2490 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002491 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002492 if v:
2493 result[k] = v
2494 else:
2495 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002496 result[k] = getpass.getpass(
2497 "Enter password for %s key> " % k).strip()
2498 if result[k]:
2499 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002500 return result
2501
2502 def UpdateAndReadFile(self, current):
2503 if not self.editor or not self.pwfile:
2504 return self.PromptResult(current)
2505
2506 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002507 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002508 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2509 f.write("# (Additional spaces are harmless.)\n\n")
2510
2511 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002512 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002513 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002514 f.write("[[[ %s ]]] %s\n" % (v, k))
2515 if not v and first_line is None:
2516 # position cursor on first line with no password.
2517 first_line = i + 4
2518 f.close()
2519
Tao Bao986ee862018-10-04 15:46:16 -07002520 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002521
2522 return self.ReadFile()
2523
2524 def ReadFile(self):
2525 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002526 if self.pwfile is None:
2527 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002528 try:
2529 f = open(self.pwfile, "r")
2530 for line in f:
2531 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002532 if not line or line[0] == '#':
2533 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002534 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2535 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002536 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002537 else:
2538 result[m.group(2)] = m.group(1)
2539 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002540 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002541 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002542 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002543 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002544
2545
Dan Albert8e0178d2015-01-27 15:53:15 -08002546def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2547 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002548
2549 # http://b/18015246
2550 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2551 # for files larger than 2GiB. We can work around this by adjusting their
2552 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2553 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2554 # it isn't clear to me exactly what circumstances cause this).
2555 # `zipfile.write()` must be used directly to work around this.
2556 #
2557 # This mess can be avoided if we port to python3.
2558 saved_zip64_limit = zipfile.ZIP64_LIMIT
2559 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2560
2561 if compress_type is None:
2562 compress_type = zip_file.compression
2563 if arcname is None:
2564 arcname = filename
2565
2566 saved_stat = os.stat(filename)
2567
2568 try:
2569 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2570 # file to be zipped and reset it when we're done.
2571 os.chmod(filename, perms)
2572
2573 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002574 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2575 # intentional. zip stores datetimes in local time without a time zone
2576 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2577 # in the zip archive.
2578 local_epoch = datetime.datetime.fromtimestamp(0)
2579 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002580 os.utime(filename, (timestamp, timestamp))
2581
2582 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2583 finally:
2584 os.chmod(filename, saved_stat.st_mode)
2585 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2586 zipfile.ZIP64_LIMIT = saved_zip64_limit
2587
2588
Tao Bao58c1b962015-05-20 09:32:18 -07002589def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002590 compress_type=None):
2591 """Wrap zipfile.writestr() function to work around the zip64 limit.
2592
2593 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2594 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2595 when calling crc32(bytes).
2596
2597 But it still works fine to write a shorter string into a large zip file.
2598 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2599 when we know the string won't be too long.
2600 """
2601
2602 saved_zip64_limit = zipfile.ZIP64_LIMIT
2603 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2604
2605 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2606 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002607 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002608 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002609 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002610 else:
Tao Baof3282b42015-04-01 11:21:55 -07002611 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002612 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2613 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2614 # such a case (since
2615 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2616 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2617 # permission bits. We follow the logic in Python 3 to get consistent
2618 # behavior between using the two versions.
2619 if not zinfo.external_attr:
2620 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002621
2622 # If compress_type is given, it overrides the value in zinfo.
2623 if compress_type is not None:
2624 zinfo.compress_type = compress_type
2625
Tao Bao58c1b962015-05-20 09:32:18 -07002626 # If perms is given, it has a priority.
2627 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002628 # If perms doesn't set the file type, mark it as a regular file.
2629 if perms & 0o770000 == 0:
2630 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002631 zinfo.external_attr = perms << 16
2632
Tao Baof3282b42015-04-01 11:21:55 -07002633 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002634 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2635
Dan Albert8b72aef2015-03-23 19:13:21 -07002636 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002637 zipfile.ZIP64_LIMIT = saved_zip64_limit
2638
2639
Tao Bao89d7ab22017-12-14 17:05:33 -08002640def ZipDelete(zip_filename, entries):
2641 """Deletes entries from a ZIP file.
2642
2643 Since deleting entries from a ZIP file is not supported, it shells out to
2644 'zip -d'.
2645
2646 Args:
2647 zip_filename: The name of the ZIP file.
2648 entries: The name of the entry, or the list of names to be deleted.
2649
2650 Raises:
2651 AssertionError: In case of non-zero return from 'zip'.
2652 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002653 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002654 entries = [entries]
2655 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002656 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002657
2658
Tao Baof3282b42015-04-01 11:21:55 -07002659def ZipClose(zip_file):
2660 # http://b/18015246
2661 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2662 # central directory.
2663 saved_zip64_limit = zipfile.ZIP64_LIMIT
2664 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2665
2666 zip_file.close()
2667
2668 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002669
2670
2671class DeviceSpecificParams(object):
2672 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002673
Doug Zongker05d3dea2009-06-22 11:32:31 -07002674 def __init__(self, **kwargs):
2675 """Keyword arguments to the constructor become attributes of this
2676 object, which is passed to all functions in the device-specific
2677 module."""
Tao Bao38884282019-07-10 22:20:56 -07002678 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002679 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002680 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002681
2682 if self.module is None:
2683 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002684 if not path:
2685 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002686 try:
2687 if os.path.isdir(path):
2688 info = imp.find_module("releasetools", [path])
2689 else:
2690 d, f = os.path.split(path)
2691 b, x = os.path.splitext(f)
2692 if x == ".py":
2693 f = b
2694 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002695 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002696 self.module = imp.load_module("device_specific", *info)
2697 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002698 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002699
2700 def _DoCall(self, function_name, *args, **kwargs):
2701 """Call the named function in the device-specific module, passing
2702 the given args and kwargs. The first argument to the call will be
2703 the DeviceSpecific object itself. If there is no module, or the
2704 module does not define the function, return the value of the
2705 'default' kwarg (which itself defaults to None)."""
2706 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002707 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002708 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2709
2710 def FullOTA_Assertions(self):
2711 """Called after emitting the block of assertions at the top of a
2712 full OTA package. Implementations can add whatever additional
2713 assertions they like."""
2714 return self._DoCall("FullOTA_Assertions")
2715
Doug Zongkere5ff5902012-01-17 10:55:37 -08002716 def FullOTA_InstallBegin(self):
2717 """Called at the start of full OTA installation."""
2718 return self._DoCall("FullOTA_InstallBegin")
2719
Yifan Hong10c530d2018-12-27 17:34:18 -08002720 def FullOTA_GetBlockDifferences(self):
2721 """Called during full OTA installation and verification.
2722 Implementation should return a list of BlockDifference objects describing
2723 the update on each additional partitions.
2724 """
2725 return self._DoCall("FullOTA_GetBlockDifferences")
2726
Doug Zongker05d3dea2009-06-22 11:32:31 -07002727 def FullOTA_InstallEnd(self):
2728 """Called at the end of full OTA installation; typically this is
2729 used to install the image for the device's baseband processor."""
2730 return self._DoCall("FullOTA_InstallEnd")
2731
2732 def IncrementalOTA_Assertions(self):
2733 """Called after emitting the block of assertions at the top of an
2734 incremental OTA package. Implementations can add whatever
2735 additional assertions they like."""
2736 return self._DoCall("IncrementalOTA_Assertions")
2737
Doug Zongkere5ff5902012-01-17 10:55:37 -08002738 def IncrementalOTA_VerifyBegin(self):
2739 """Called at the start of the verification phase of incremental
2740 OTA installation; additional checks can be placed here to abort
2741 the script before any changes are made."""
2742 return self._DoCall("IncrementalOTA_VerifyBegin")
2743
Doug Zongker05d3dea2009-06-22 11:32:31 -07002744 def IncrementalOTA_VerifyEnd(self):
2745 """Called at the end of the verification phase of incremental OTA
2746 installation; additional checks can be placed here to abort the
2747 script before any changes are made."""
2748 return self._DoCall("IncrementalOTA_VerifyEnd")
2749
Doug Zongkere5ff5902012-01-17 10:55:37 -08002750 def IncrementalOTA_InstallBegin(self):
2751 """Called at the start of incremental OTA installation (after
2752 verification is complete)."""
2753 return self._DoCall("IncrementalOTA_InstallBegin")
2754
Yifan Hong10c530d2018-12-27 17:34:18 -08002755 def IncrementalOTA_GetBlockDifferences(self):
2756 """Called during incremental OTA installation and verification.
2757 Implementation should return a list of BlockDifference objects describing
2758 the update on each additional partitions.
2759 """
2760 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2761
Doug Zongker05d3dea2009-06-22 11:32:31 -07002762 def IncrementalOTA_InstallEnd(self):
2763 """Called at the end of incremental OTA installation; typically
2764 this is used to install the image for the device's baseband
2765 processor."""
2766 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002767
Tao Bao9bc6bb22015-11-09 16:58:28 -08002768 def VerifyOTA_Assertions(self):
2769 return self._DoCall("VerifyOTA_Assertions")
2770
Tao Bao76def242017-11-21 09:25:31 -08002771
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002772class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002773 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002774 self.name = name
2775 self.data = data
2776 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002777 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002778 self.sha1 = sha1(data).hexdigest()
2779
2780 @classmethod
2781 def FromLocalFile(cls, name, diskname):
2782 f = open(diskname, "rb")
2783 data = f.read()
2784 f.close()
2785 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002786
2787 def WriteToTemp(self):
2788 t = tempfile.NamedTemporaryFile()
2789 t.write(self.data)
2790 t.flush()
2791 return t
2792
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002793 def WriteToDir(self, d):
2794 with open(os.path.join(d, self.name), "wb") as fp:
2795 fp.write(self.data)
2796
Geremy Condra36bd3652014-02-06 19:45:10 -08002797 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002798 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002799
Tao Bao76def242017-11-21 09:25:31 -08002800
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002801DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002802 ".gz": "imgdiff",
2803 ".zip": ["imgdiff", "-z"],
2804 ".jar": ["imgdiff", "-z"],
2805 ".apk": ["imgdiff", "-z"],
2806 ".img": "imgdiff",
2807}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002808
Tao Bao76def242017-11-21 09:25:31 -08002809
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002810class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002811 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002812 self.tf = tf
2813 self.sf = sf
2814 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002815 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002816
2817 def ComputePatch(self):
2818 """Compute the patch (as a string of data) needed to turn sf into
2819 tf. Returns the same tuple as GetPatch()."""
2820
2821 tf = self.tf
2822 sf = self.sf
2823
Doug Zongker24cd2802012-08-14 16:36:15 -07002824 if self.diff_program:
2825 diff_program = self.diff_program
2826 else:
2827 ext = os.path.splitext(tf.name)[1]
2828 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002829
2830 ttemp = tf.WriteToTemp()
2831 stemp = sf.WriteToTemp()
2832
2833 ext = os.path.splitext(tf.name)[1]
2834
2835 try:
2836 ptemp = tempfile.NamedTemporaryFile()
2837 if isinstance(diff_program, list):
2838 cmd = copy.copy(diff_program)
2839 else:
2840 cmd = [diff_program]
2841 cmd.append(stemp.name)
2842 cmd.append(ttemp.name)
2843 cmd.append(ptemp.name)
2844 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002845 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002846
Doug Zongkerf8340082014-08-05 10:39:37 -07002847 def run():
2848 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002849 if e:
2850 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002851 th = threading.Thread(target=run)
2852 th.start()
2853 th.join(timeout=300) # 5 mins
2854 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002855 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002856 p.terminate()
2857 th.join(5)
2858 if th.is_alive():
2859 p.kill()
2860 th.join()
2861
Tianjie Xua2a9f992018-01-05 15:15:54 -08002862 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002863 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002864 self.patch = None
2865 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002866 diff = ptemp.read()
2867 finally:
2868 ptemp.close()
2869 stemp.close()
2870 ttemp.close()
2871
2872 self.patch = diff
2873 return self.tf, self.sf, self.patch
2874
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002875 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002876 """Returns a tuple of (target_file, source_file, patch_data).
2877
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002878 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002879 computing the patch failed.
2880 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002881 return self.tf, self.sf, self.patch
2882
2883
2884def ComputeDifferences(diffs):
2885 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002886 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002887
2888 # Do the largest files first, to try and reduce the long-pole effect.
2889 by_size = [(i.tf.size, i) for i in diffs]
2890 by_size.sort(reverse=True)
2891 by_size = [i[1] for i in by_size]
2892
2893 lock = threading.Lock()
2894 diff_iter = iter(by_size) # accessed under lock
2895
2896 def worker():
2897 try:
2898 lock.acquire()
2899 for d in diff_iter:
2900 lock.release()
2901 start = time.time()
2902 d.ComputePatch()
2903 dur = time.time() - start
2904 lock.acquire()
2905
2906 tf, sf, patch = d.GetPatch()
2907 if sf.name == tf.name:
2908 name = tf.name
2909 else:
2910 name = "%s (%s)" % (tf.name, sf.name)
2911 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002912 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002913 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002914 logger.info(
2915 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2916 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002917 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002918 except Exception:
2919 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002920 raise
2921
2922 # start worker threads; wait for them all to finish.
2923 threads = [threading.Thread(target=worker)
2924 for i in range(OPTIONS.worker_threads)]
2925 for th in threads:
2926 th.start()
2927 while threads:
2928 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002929
2930
Dan Albert8b72aef2015-03-23 19:13:21 -07002931class BlockDifference(object):
2932 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002933 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002934 self.tgt = tgt
2935 self.src = src
2936 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002937 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002938 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002939
Tao Baodd2a5892015-03-12 12:32:37 -07002940 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002941 version = max(
2942 int(i) for i in
2943 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002944 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002945 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002946
Tianjie Xu41976c72019-07-03 13:57:01 -07002947 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2948 version=self.version,
2949 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002950 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002951 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002952 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002953 self.touched_src_ranges = b.touched_src_ranges
2954 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002955
Yifan Hong10c530d2018-12-27 17:34:18 -08002956 # On devices with dynamic partitions, for new partitions,
2957 # src is None but OPTIONS.source_info_dict is not.
2958 if OPTIONS.source_info_dict is None:
2959 is_dynamic_build = OPTIONS.info_dict.get(
2960 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002961 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002962 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002963 is_dynamic_build = OPTIONS.source_info_dict.get(
2964 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002965 is_dynamic_source = partition in shlex.split(
2966 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002967
Yifan Hongbb2658d2019-01-25 12:30:58 -08002968 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002969 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2970
Yifan Hongbb2658d2019-01-25 12:30:58 -08002971 # For dynamic partitions builds, check partition list in both source
2972 # and target build because new partitions may be added, and existing
2973 # partitions may be removed.
2974 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2975
Yifan Hong10c530d2018-12-27 17:34:18 -08002976 if is_dynamic:
2977 self.device = 'map_partition("%s")' % partition
2978 else:
2979 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002980 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2981 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002982 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002983 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2984 OPTIONS.source_info_dict)
2985 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002986
Tao Baod8d14be2016-02-04 14:26:02 -08002987 @property
2988 def required_cache(self):
2989 return self._required_cache
2990
Tao Bao76def242017-11-21 09:25:31 -08002991 def WriteScript(self, script, output_zip, progress=None,
2992 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002993 if not self.src:
2994 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002995 script.Print("Patching %s image unconditionally..." % (self.partition,))
2996 else:
2997 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002998
Dan Albert8b72aef2015-03-23 19:13:21 -07002999 if progress:
3000 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003001 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003002
3003 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003004 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003005
Tao Bao9bc6bb22015-11-09 16:58:28 -08003006 def WriteStrictVerifyScript(self, script):
3007 """Verify all the blocks in the care_map, including clobbered blocks.
3008
3009 This differs from the WriteVerifyScript() function: a) it prints different
3010 error messages; b) it doesn't allow half-way updated images to pass the
3011 verification."""
3012
3013 partition = self.partition
3014 script.Print("Verifying %s..." % (partition,))
3015 ranges = self.tgt.care_map
3016 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003017 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003018 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3019 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003020 self.device, ranges_str,
3021 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003022 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003023 script.AppendExtra("")
3024
Tao Baod522bdc2016-04-12 15:53:16 -07003025 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003026 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003027
3028 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003029 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003030 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003031
3032 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003033 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003034 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003035 ranges = self.touched_src_ranges
3036 expected_sha1 = self.touched_src_sha1
3037 else:
3038 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3039 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003040
3041 # No blocks to be checked, skipping.
3042 if not ranges:
3043 return
3044
Tao Bao5ece99d2015-05-12 11:42:31 -07003045 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003046 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003047 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003048 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3049 '"%s.patch.dat")) then' % (
3050 self.device, ranges_str, expected_sha1,
3051 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003052 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003053 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003054
Tianjie Xufc3422a2015-12-15 11:53:59 -08003055 if self.version >= 4:
3056
3057 # Bug: 21124327
3058 # When generating incrementals for the system and vendor partitions in
3059 # version 4 or newer, explicitly check the first block (which contains
3060 # the superblock) of the partition to see if it's what we expect. If
3061 # this check fails, give an explicit log message about the partition
3062 # having been remounted R/W (the most likely explanation).
3063 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003064 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003065
3066 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003067 if partition == "system":
3068 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3069 else:
3070 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003071 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003072 'ifelse (block_image_recover({device}, "{ranges}") && '
3073 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003074 'package_extract_file("{partition}.transfer.list"), '
3075 '"{partition}.new.dat", "{partition}.patch.dat"), '
3076 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003077 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003078 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003079 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003080
Tao Baodd2a5892015-03-12 12:32:37 -07003081 # Abort the OTA update. Note that the incremental OTA cannot be applied
3082 # even if it may match the checksum of the target partition.
3083 # a) If version < 3, operations like move and erase will make changes
3084 # unconditionally and damage the partition.
3085 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003086 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003087 if partition == "system":
3088 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3089 else:
3090 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3091 script.AppendExtra((
3092 'abort("E%d: %s partition has unexpected contents");\n'
3093 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003094
Yifan Hong10c530d2018-12-27 17:34:18 -08003095 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003096 partition = self.partition
3097 script.Print('Verifying the updated %s image...' % (partition,))
3098 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3099 ranges = self.tgt.care_map
3100 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003101 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003102 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003103 self.device, ranges_str,
3104 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003105
3106 # Bug: 20881595
3107 # Verify that extended blocks are really zeroed out.
3108 if self.tgt.extended:
3109 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003110 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003111 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003112 self.device, ranges_str,
3113 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003114 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003115 if partition == "system":
3116 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3117 else:
3118 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003119 script.AppendExtra(
3120 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003121 ' abort("E%d: %s partition has unexpected non-zero contents after '
3122 'OTA update");\n'
3123 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003124 else:
3125 script.Print('Verified the updated %s image.' % (partition,))
3126
Tianjie Xu209db462016-05-24 17:34:52 -07003127 if partition == "system":
3128 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3129 else:
3130 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3131
Tao Bao5fcaaef2015-06-01 13:40:49 -07003132 script.AppendExtra(
3133 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003134 ' abort("E%d: %s partition has unexpected contents after OTA '
3135 'update");\n'
3136 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003137
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003138 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003139 ZipWrite(output_zip,
3140 '{}.transfer.list'.format(self.path),
3141 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003142
Tao Bao76def242017-11-21 09:25:31 -08003143 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3144 # its size. Quailty 9 almost triples the compression time but doesn't
3145 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003146 # zip | brotli(quality 6) | brotli(quality 9)
3147 # compressed_size: 942M | 869M (~8% reduced) | 854M
3148 # compression_time: 75s | 265s | 719s
3149 # decompression_time: 15s | 25s | 25s
3150
3151 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003152 brotli_cmd = ['brotli', '--quality=6',
3153 '--output={}.new.dat.br'.format(self.path),
3154 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003155 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003156 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003157
3158 new_data_name = '{}.new.dat.br'.format(self.partition)
3159 ZipWrite(output_zip,
3160 '{}.new.dat.br'.format(self.path),
3161 new_data_name,
3162 compress_type=zipfile.ZIP_STORED)
3163 else:
3164 new_data_name = '{}.new.dat'.format(self.partition)
3165 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3166
Dan Albert8e0178d2015-01-27 15:53:15 -08003167 ZipWrite(output_zip,
3168 '{}.patch.dat'.format(self.path),
3169 '{}.patch.dat'.format(self.partition),
3170 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003171
Tianjie Xu209db462016-05-24 17:34:52 -07003172 if self.partition == "system":
3173 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3174 else:
3175 code = ErrorCode.VENDOR_UPDATE_FAILURE
3176
Yifan Hong10c530d2018-12-27 17:34:18 -08003177 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003178 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003179 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003180 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003181 device=self.device, partition=self.partition,
3182 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003183 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003184
Kelvin Zhang0876c412020-06-23 15:06:58 -04003185 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003186 data = source.ReadRangeSet(ranges)
3187 ctx = sha1()
3188
3189 for p in data:
3190 ctx.update(p)
3191
3192 return ctx.hexdigest()
3193
Kelvin Zhang0876c412020-06-23 15:06:58 -04003194 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003195 """Return the hash value for all zero blocks."""
3196 zero_block = '\x00' * 4096
3197 ctx = sha1()
3198 for _ in range(num_blocks):
3199 ctx.update(zero_block)
3200
3201 return ctx.hexdigest()
3202
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003203
Tianjie Xu41976c72019-07-03 13:57:01 -07003204# Expose these two classes to support vendor-specific scripts
3205DataImage = images.DataImage
3206EmptyImage = images.EmptyImage
3207
Tao Bao76def242017-11-21 09:25:31 -08003208
Doug Zongker96a57e72010-09-26 14:57:41 -07003209# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003210PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003211 "ext4": "EMMC",
3212 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003213 "f2fs": "EMMC",
3214 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003215}
Doug Zongker96a57e72010-09-26 14:57:41 -07003216
Kelvin Zhang0876c412020-06-23 15:06:58 -04003217
Yifan Hongbdb32012020-05-07 12:38:53 -07003218def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3219 """
3220 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3221 backwards compatibility. It aborts if the fstab entry has slotselect option
3222 (unless check_no_slot is explicitly set to False).
3223 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003224 fstab = info["fstab"]
3225 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003226 if check_no_slot:
3227 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003228 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003229 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3230 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003231 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003232
3233
Yifan Hongbdb32012020-05-07 12:38:53 -07003234def GetTypeAndDeviceExpr(mount_point, info):
3235 """
3236 Return the filesystem of the partition, and an edify expression that evaluates
3237 to the device at runtime.
3238 """
3239 fstab = info["fstab"]
3240 if fstab:
3241 p = fstab[mount_point]
3242 device_expr = '"%s"' % fstab[mount_point].device
3243 if p.slotselect:
3244 device_expr = 'add_slot_suffix(%s)' % device_expr
3245 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003246 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003247
3248
3249def GetEntryForDevice(fstab, device):
3250 """
3251 Returns:
3252 The first entry in fstab whose device is the given value.
3253 """
3254 if not fstab:
3255 return None
3256 for mount_point in fstab:
3257 if fstab[mount_point].device == device:
3258 return fstab[mount_point]
3259 return None
3260
Kelvin Zhang0876c412020-06-23 15:06:58 -04003261
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003262def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003263 """Parses and converts a PEM-encoded certificate into DER-encoded.
3264
3265 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3266
3267 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003268 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003269 """
3270 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003271 save = False
3272 for line in data.split("\n"):
3273 if "--END CERTIFICATE--" in line:
3274 break
3275 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003276 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003277 if "--BEGIN CERTIFICATE--" in line:
3278 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003279 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003280 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003281
Tao Bao04e1f012018-02-04 12:13:35 -08003282
3283def ExtractPublicKey(cert):
3284 """Extracts the public key (PEM-encoded) from the given certificate file.
3285
3286 Args:
3287 cert: The certificate filename.
3288
3289 Returns:
3290 The public key string.
3291
3292 Raises:
3293 AssertionError: On non-zero return from 'openssl'.
3294 """
3295 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3296 # While openssl 1.1 writes the key into the given filename followed by '-out',
3297 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3298 # stdout instead.
3299 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3300 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3301 pubkey, stderrdata = proc.communicate()
3302 assert proc.returncode == 0, \
3303 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3304 return pubkey
3305
3306
Tao Bao1ac886e2019-06-26 11:58:22 -07003307def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003308 """Extracts the AVB public key from the given public or private key.
3309
3310 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003311 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003312 key: The input key file, which should be PEM-encoded public or private key.
3313
3314 Returns:
3315 The path to the extracted AVB public key file.
3316 """
3317 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3318 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003319 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003320 return output
3321
3322
Doug Zongker412c02f2014-02-13 10:58:24 -08003323def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3324 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003325 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003326
Tao Bao6d5d6232018-03-09 17:04:42 -08003327 Most of the space in the boot and recovery images is just the kernel, which is
3328 identical for the two, so the resulting patch should be efficient. Add it to
3329 the output zip, along with a shell script that is run from init.rc on first
3330 boot to actually do the patching and install the new recovery image.
3331
3332 Args:
3333 input_dir: The top-level input directory of the target-files.zip.
3334 output_sink: The callback function that writes the result.
3335 recovery_img: File object for the recovery image.
3336 boot_img: File objects for the boot image.
3337 info_dict: A dict returned by common.LoadInfoDict() on the input
3338 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003339 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003340 if info_dict is None:
3341 info_dict = OPTIONS.info_dict
3342
Tao Bao6d5d6232018-03-09 17:04:42 -08003343 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003344 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3345
3346 if board_uses_vendorimage:
3347 # In this case, the output sink is rooted at VENDOR
3348 recovery_img_path = "etc/recovery.img"
3349 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3350 sh_dir = "bin"
3351 else:
3352 # In this case the output sink is rooted at SYSTEM
3353 recovery_img_path = "vendor/etc/recovery.img"
3354 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3355 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003356
Tao Baof2cffbd2015-07-22 12:33:18 -07003357 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003358 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003359
3360 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003361 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003362 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003363 # With system-root-image, boot and recovery images will have mismatching
3364 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3365 # to handle such a case.
3366 if system_root_image:
3367 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003368 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003369 assert not os.path.exists(path)
3370 else:
3371 diff_program = ["imgdiff"]
3372 if os.path.exists(path):
3373 diff_program.append("-b")
3374 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003375 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003376 else:
3377 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003378
3379 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3380 _, _, patch = d.ComputePatch()
3381 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003382
Dan Albertebb19aa2015-03-27 19:11:53 -07003383 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003384 # The following GetTypeAndDevice()s need to use the path in the target
3385 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003386 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3387 check_no_slot=False)
3388 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3389 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003390 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003391 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003392
Tao Baof2cffbd2015-07-22 12:33:18 -07003393 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003394
3395 # Note that we use /vendor to refer to the recovery resources. This will
3396 # work for a separate vendor partition mounted at /vendor or a
3397 # /system/vendor subdirectory on the system partition, for which init will
3398 # create a symlink from /vendor to /system/vendor.
3399
3400 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003401if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3402 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003403 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003404 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3405 log -t recovery "Installing new recovery image: succeeded" || \\
3406 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003407else
3408 log -t recovery "Recovery image already installed"
3409fi
3410""" % {'type': recovery_type,
3411 'device': recovery_device,
3412 'sha1': recovery_img.sha1,
3413 'size': recovery_img.size}
3414 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003415 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003416if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3417 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003418 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003419 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3420 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3421 log -t recovery "Installing new recovery image: succeeded" || \\
3422 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003423else
3424 log -t recovery "Recovery image already installed"
3425fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003426""" % {'boot_size': boot_img.size,
3427 'boot_sha1': boot_img.sha1,
3428 'recovery_size': recovery_img.size,
3429 'recovery_sha1': recovery_img.sha1,
3430 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003431 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003432 'recovery_type': recovery_type,
3433 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003434 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003435
Bill Peckhame868aec2019-09-17 17:06:47 -07003436 # The install script location moved from /system/etc to /system/bin in the L
3437 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3438 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003439
Tao Bao32fcdab2018-10-12 10:30:39 -07003440 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003441
Tao Baoda30cfa2017-12-01 16:19:46 -08003442 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003443
3444
3445class DynamicPartitionUpdate(object):
3446 def __init__(self, src_group=None, tgt_group=None, progress=None,
3447 block_difference=None):
3448 self.src_group = src_group
3449 self.tgt_group = tgt_group
3450 self.progress = progress
3451 self.block_difference = block_difference
3452
3453 @property
3454 def src_size(self):
3455 if not self.block_difference:
3456 return 0
3457 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3458
3459 @property
3460 def tgt_size(self):
3461 if not self.block_difference:
3462 return 0
3463 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3464
3465 @staticmethod
3466 def _GetSparseImageSize(img):
3467 if not img:
3468 return 0
3469 return img.blocksize * img.total_blocks
3470
3471
3472class DynamicGroupUpdate(object):
3473 def __init__(self, src_size=None, tgt_size=None):
3474 # None: group does not exist. 0: no size limits.
3475 self.src_size = src_size
3476 self.tgt_size = tgt_size
3477
3478
3479class DynamicPartitionsDifference(object):
3480 def __init__(self, info_dict, block_diffs, progress_dict=None,
3481 source_info_dict=None):
3482 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003483 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003484
3485 self._remove_all_before_apply = False
3486 if source_info_dict is None:
3487 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003488 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003489
Tao Baof1113e92019-06-18 12:10:14 -07003490 block_diff_dict = collections.OrderedDict(
3491 [(e.partition, e) for e in block_diffs])
3492
Yifan Hong10c530d2018-12-27 17:34:18 -08003493 assert len(block_diff_dict) == len(block_diffs), \
3494 "Duplicated BlockDifference object for {}".format(
3495 [partition for partition, count in
3496 collections.Counter(e.partition for e in block_diffs).items()
3497 if count > 1])
3498
Yifan Hong79997e52019-01-23 16:56:19 -08003499 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003500
3501 for p, block_diff in block_diff_dict.items():
3502 self._partition_updates[p] = DynamicPartitionUpdate()
3503 self._partition_updates[p].block_difference = block_diff
3504
3505 for p, progress in progress_dict.items():
3506 if p in self._partition_updates:
3507 self._partition_updates[p].progress = progress
3508
3509 tgt_groups = shlex.split(info_dict.get(
3510 "super_partition_groups", "").strip())
3511 src_groups = shlex.split(source_info_dict.get(
3512 "super_partition_groups", "").strip())
3513
3514 for g in tgt_groups:
3515 for p in shlex.split(info_dict.get(
3516 "super_%s_partition_list" % g, "").strip()):
3517 assert p in self._partition_updates, \
3518 "{} is in target super_{}_partition_list but no BlockDifference " \
3519 "object is provided.".format(p, g)
3520 self._partition_updates[p].tgt_group = g
3521
3522 for g in src_groups:
3523 for p in shlex.split(source_info_dict.get(
3524 "super_%s_partition_list" % g, "").strip()):
3525 assert p in self._partition_updates, \
3526 "{} is in source super_{}_partition_list but no BlockDifference " \
3527 "object is provided.".format(p, g)
3528 self._partition_updates[p].src_group = g
3529
Yifan Hong45433e42019-01-18 13:55:25 -08003530 target_dynamic_partitions = set(shlex.split(info_dict.get(
3531 "dynamic_partition_list", "").strip()))
3532 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3533 if u.tgt_size)
3534 assert block_diffs_with_target == target_dynamic_partitions, \
3535 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3536 list(target_dynamic_partitions), list(block_diffs_with_target))
3537
3538 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3539 "dynamic_partition_list", "").strip()))
3540 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3541 if u.src_size)
3542 assert block_diffs_with_source == source_dynamic_partitions, \
3543 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3544 list(source_dynamic_partitions), list(block_diffs_with_source))
3545
Yifan Hong10c530d2018-12-27 17:34:18 -08003546 if self._partition_updates:
3547 logger.info("Updating dynamic partitions %s",
3548 self._partition_updates.keys())
3549
Yifan Hong79997e52019-01-23 16:56:19 -08003550 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003551
3552 for g in tgt_groups:
3553 self._group_updates[g] = DynamicGroupUpdate()
3554 self._group_updates[g].tgt_size = int(info_dict.get(
3555 "super_%s_group_size" % g, "0").strip())
3556
3557 for g in src_groups:
3558 if g not in self._group_updates:
3559 self._group_updates[g] = DynamicGroupUpdate()
3560 self._group_updates[g].src_size = int(source_info_dict.get(
3561 "super_%s_group_size" % g, "0").strip())
3562
3563 self._Compute()
3564
3565 def WriteScript(self, script, output_zip, write_verify_script=False):
3566 script.Comment('--- Start patching dynamic partitions ---')
3567 for p, u in self._partition_updates.items():
3568 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3569 script.Comment('Patch partition %s' % p)
3570 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3571 write_verify_script=False)
3572
3573 op_list_path = MakeTempFile()
3574 with open(op_list_path, 'w') as f:
3575 for line in self._op_list:
3576 f.write('{}\n'.format(line))
3577
3578 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3579
3580 script.Comment('Update dynamic partition metadata')
3581 script.AppendExtra('assert(update_dynamic_partitions('
3582 'package_extract_file("dynamic_partitions_op_list")));')
3583
3584 if write_verify_script:
3585 for p, u in self._partition_updates.items():
3586 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3587 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003588 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003589
3590 for p, u in self._partition_updates.items():
3591 if u.tgt_size and u.src_size <= u.tgt_size:
3592 script.Comment('Patch partition %s' % p)
3593 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3594 write_verify_script=write_verify_script)
3595 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003596 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003597
3598 script.Comment('--- End patching dynamic partitions ---')
3599
3600 def _Compute(self):
3601 self._op_list = list()
3602
3603 def append(line):
3604 self._op_list.append(line)
3605
3606 def comment(line):
3607 self._op_list.append("# %s" % line)
3608
3609 if self._remove_all_before_apply:
3610 comment('Remove all existing dynamic partitions and groups before '
3611 'applying full OTA')
3612 append('remove_all_groups')
3613
3614 for p, u in self._partition_updates.items():
3615 if u.src_group and not u.tgt_group:
3616 append('remove %s' % p)
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 %s to default' % (p, u.src_group))
3621 append('move %s default' % p)
3622
3623 for p, u in self._partition_updates.items():
3624 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3625 comment('Shrink partition %s from %d to %d' %
3626 (p, u.src_size, u.tgt_size))
3627 append('resize %s %s' % (p, u.tgt_size))
3628
3629 for g, u in self._group_updates.items():
3630 if u.src_size is not None and u.tgt_size is None:
3631 append('remove_group %s' % g)
3632 if (u.src_size is not None and u.tgt_size is not None and
3633 u.src_size > u.tgt_size):
3634 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3635 append('resize_group %s %d' % (g, u.tgt_size))
3636
3637 for g, u in self._group_updates.items():
3638 if u.src_size is None and u.tgt_size is not None:
3639 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3640 append('add_group %s %d' % (g, u.tgt_size))
3641 if (u.src_size is not None and u.tgt_size is not None and
3642 u.src_size < u.tgt_size):
3643 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3644 append('resize_group %s %d' % (g, u.tgt_size))
3645
3646 for p, u in self._partition_updates.items():
3647 if u.tgt_group and not u.src_group:
3648 comment('Add partition %s to group %s' % (p, u.tgt_group))
3649 append('add %s %s' % (p, u.tgt_group))
3650
3651 for p, u in self._partition_updates.items():
3652 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003653 comment('Grow partition %s from %d to %d' %
3654 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003655 append('resize %s %d' % (p, u.tgt_size))
3656
3657 for p, u in self._partition_updates.items():
3658 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3659 comment('Move partition %s from default to %s' %
3660 (p, u.tgt_group))
3661 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003662
3663
Yifan Hong85ac5012021-01-07 14:43:46 -08003664def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003665 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003666 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003667
3668 Args:
3669 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3670
3671 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003672 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003673 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003674 tmp_dir = MakeTempDir('boot_', suffix='.img')
3675 try:
3676 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3677 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3678 if not os.path.isfile(ramdisk):
3679 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3680 return None
3681 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3682 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3683
3684 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3685 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3686 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3687 # the host environment.
3688 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3689 cwd=extracted_ramdisk)
3690
Yifan Hongc65a0542021-01-07 14:21:01 -08003691 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3692 prop_file = os.path.join(extracted_ramdisk, search_path)
3693 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003694 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003695 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3696
Yifan Hong7dc51172021-01-12 11:27:39 -08003697 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003698
Yifan Hong85ac5012021-01-07 14:43:46 -08003699 except ExternalError as e:
3700 logger.warning('Unable to get boot image build props: %s', e)
3701 return None
3702
3703
3704def GetBootImageTimestamp(boot_img):
3705 """
3706 Get timestamp from ramdisk within the boot image
3707
3708 Args:
3709 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3710
3711 Return:
3712 An integer that corresponds to the timestamp of the boot image, or None
3713 if file has unknown format. Raise exception if an unexpected error has
3714 occurred.
3715 """
3716 prop_file = GetBootImageBuildProp(boot_img)
3717 if not prop_file:
3718 return None
3719
3720 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3721 if props is None:
3722 return None
3723
3724 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003725 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3726 if timestamp:
3727 return int(timestamp)
3728 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3729 return None
3730
3731 except ExternalError as e:
3732 logger.warning('Unable to get boot image timestamp: %s', e)
3733 return None