blob: c550490b29298c172ba35b6589dcc1b46fe2918c [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
jiajia tangf3f842b2021-03-17 21:49:44 +0800654class RamdiskFormat(object):
655 LZ4 = 1
656 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800657
Tao Bao410ad8b2018-08-24 12:08:38 -0700658def LoadInfoDict(input_file, repacking=False):
659 """Loads the key/value pairs from the given input target_files.
660
Tianjiea85bdf02020-07-29 11:56:19 -0700661 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700662 checks and returns the parsed key/value pairs for to the given build. It's
663 usually called early when working on input target_files files, e.g. when
664 generating OTAs, or signing builds. Note that the function may be called
665 against an old target_files file (i.e. from past dessert releases). So the
666 property parsing needs to be backward compatible.
667
668 In a `META/misc_info.txt`, a few properties are stored as links to the files
669 in the PRODUCT_OUT directory. It works fine with the build system. However,
670 they are no longer available when (re)generating images from target_files zip.
671 When `repacking` is True, redirect these properties to the actual files in the
672 unzipped directory.
673
674 Args:
675 input_file: The input target_files file, which could be an open
676 zipfile.ZipFile instance, or a str for the dir that contains the files
677 unzipped from a target_files file.
678 repacking: Whether it's trying repack an target_files file after loading the
679 info dict (default: False). If so, it will rewrite a few loaded
680 properties (e.g. selinux_fc, root_dir) to point to the actual files in
681 target_files file. When doing repacking, `input_file` must be a dir.
682
683 Returns:
684 A dict that contains the parsed key/value pairs.
685
686 Raises:
687 AssertionError: On invalid input arguments.
688 ValueError: On malformed input values.
689 """
690 if repacking:
691 assert isinstance(input_file, str), \
692 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700693
Doug Zongkerc9253822014-02-04 12:17:58 -0800694 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000695 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800696
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700697 try:
Michael Runge6e836112014-04-15 17:40:21 -0700698 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700699 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700700 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700701
Tao Bao410ad8b2018-08-24 12:08:38 -0700702 if "recovery_api_version" not in d:
703 raise ValueError("Failed to find 'recovery_api_version'")
704 if "fstab_version" not in d:
705 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800706
Tao Bao410ad8b2018-08-24 12:08:38 -0700707 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700708 # "selinux_fc" properties should point to the file_contexts files
709 # (file_contexts.bin) under META/.
710 for key in d:
711 if key.endswith("selinux_fc"):
712 fc_basename = os.path.basename(d[key])
713 fc_config = os.path.join(input_file, "META", fc_basename)
714 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700715
Daniel Norman72c626f2019-05-13 15:58:14 -0700716 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700717
Tom Cherryd14b8952018-08-09 14:26:00 -0700718 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700719 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700720 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700721 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700722
David Anderson0ec64ac2019-12-06 12:21:18 -0800723 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700724 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700725 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800726 key_name = part_name + "_base_fs_file"
727 if key_name not in d:
728 continue
729 basename = os.path.basename(d[key_name])
730 base_fs_file = os.path.join(input_file, "META", basename)
731 if os.path.exists(base_fs_file):
732 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700733 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700734 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800735 "Failed to find %s base fs file: %s", part_name, base_fs_file)
736 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700737
Doug Zongker37974732010-09-16 17:44:38 -0700738 def makeint(key):
739 if key in d:
740 d[key] = int(d[key], 0)
741
742 makeint("recovery_api_version")
743 makeint("blocksize")
744 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700745 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700746 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700747 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700748 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800749 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700750
Steve Muckle903a1ca2020-05-07 17:32:10 -0700751 boot_images = "boot.img"
752 if "boot_images" in d:
753 boot_images = d["boot_images"]
754 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400755 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700756
Tao Bao765668f2019-10-04 22:03:00 -0700757 # Load recovery fstab if applicable.
758 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tangf3f842b2021-03-17 21:49:44 +0800759 if d.get('lz4_ramdisks') == 'true':
760 ramdisk_format = RamdiskFormat.LZ4
761 else:
762 ramdisk_format = RamdiskFormat.GZ
Tianjie Xucfa86222016-03-07 16:31:19 -0800763
Tianjie Xu861f4132018-09-12 11:49:33 -0700764 # Tries to load the build props for all partitions with care_map, including
765 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800766 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800767 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000768 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800769 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700770 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800771
Tao Bao3ed35d32019-10-07 20:48:48 -0700772 # Set up the salt (based on fingerprint) that will be used when adding AVB
773 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800774 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700775 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800776 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800777 fingerprint = build_info.GetPartitionFingerprint(partition)
778 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400779 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400780 try:
781 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
782 except KeyError:
783 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700784 return d
785
Tao Baod1de6f32017-03-01 16:38:48 -0800786
Kelvin Zhang39aea442020-08-17 11:04:25 -0400787
Daniel Norman4cc9df62019-07-18 10:11:07 -0700788def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900789 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700790 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900791
Daniel Norman4cc9df62019-07-18 10:11:07 -0700792
793def LoadDictionaryFromFile(file_path):
794 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900795 return LoadDictionaryFromLines(lines)
796
797
Michael Runge6e836112014-04-15 17:40:21 -0700798def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700799 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700800 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700801 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700802 if not line or line.startswith("#"):
803 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700804 if "=" in line:
805 name, value = line.split("=", 1)
806 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700807 return d
808
Tao Baod1de6f32017-03-01 16:38:48 -0800809
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000810class PartitionBuildProps(object):
811 """The class holds the build prop of a particular partition.
812
813 This class loads the build.prop and holds the build properties for a given
814 partition. It also partially recognizes the 'import' statement in the
815 build.prop; and calculates alternative values of some specific build
816 properties during runtime.
817
818 Attributes:
819 input_file: a zipped target-file or an unzipped target-file directory.
820 partition: name of the partition.
821 props_allow_override: a list of build properties to search for the
822 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000823 build_props: a dict of build properties for the given partition.
824 prop_overrides: a set of props that are overridden by import.
825 placeholder_values: A dict of runtime variables' values to replace the
826 placeholders in the build.prop file. We expect exactly one value for
827 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800828 ramdisk_format: If name is "boot", the format of ramdisk inside the
829 boot image. Otherwise, its value is ignored.
830 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000831 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400832
Tianjie Xu9afb2212020-05-10 21:48:15 +0000833 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000834 self.input_file = input_file
835 self.partition = name
836 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000837 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000838 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000839 self.prop_overrides = set()
840 self.placeholder_values = {}
841 if placeholder_values:
842 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000843
844 @staticmethod
845 def FromDictionary(name, build_props):
846 """Constructs an instance from a build prop dictionary."""
847
848 props = PartitionBuildProps("unknown", name)
849 props.build_props = build_props.copy()
850 return props
851
852 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800853 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000854 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800855
856 if name == "boot":
jiajia tangf3f842b2021-03-17 21:49:44 +0800857 data = PartitionBuildProps._ReadBootPropFile(input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800858 else:
859 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
860
861 props = PartitionBuildProps(input_file, name, placeholder_values)
862 props._LoadBuildProp(data)
863 return props
864
865 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800866 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800867 """
868 Read build.prop for boot image from input_file.
869 Return empty string if not found.
870 """
871 try:
872 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
873 except KeyError:
874 logger.warning('Failed to read IMAGES/boot.img')
875 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800876 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800877 if prop_file is None:
878 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500879 with open(prop_file, "r") as f:
880 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800881
882 @staticmethod
883 def _ReadPartitionPropFile(input_file, name):
884 """
885 Read build.prop for name from input_file.
886 Return empty string if not found.
887 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000888 data = ''
889 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
890 '{}/build.prop'.format(name.upper())]:
891 try:
892 data = ReadFromInputFile(input_file, prop_file)
893 break
894 except KeyError:
895 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800896 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000897
Yifan Hong125d0b62020-09-24 17:07:03 -0700898 @staticmethod
899 def FromBuildPropFile(name, build_prop_file):
900 """Constructs an instance from a build prop file."""
901
902 props = PartitionBuildProps("unknown", name)
903 with open(build_prop_file) as f:
904 props._LoadBuildProp(f.read())
905 return props
906
Tianjie Xu9afb2212020-05-10 21:48:15 +0000907 def _LoadBuildProp(self, data):
908 for line in data.split('\n'):
909 line = line.strip()
910 if not line or line.startswith("#"):
911 continue
912 if line.startswith("import"):
913 overrides = self._ImportParser(line)
914 duplicates = self.prop_overrides.intersection(overrides.keys())
915 if duplicates:
916 raise ValueError('prop {} is overridden multiple times'.format(
917 ','.join(duplicates)))
918 self.prop_overrides = self.prop_overrides.union(overrides.keys())
919 self.build_props.update(overrides)
920 elif "=" in line:
921 name, value = line.split("=", 1)
922 if name in self.prop_overrides:
923 raise ValueError('prop {} is set again after overridden by import '
924 'statement'.format(name))
925 self.build_props[name] = value
926
927 def _ImportParser(self, line):
928 """Parses the build prop in a given import statement."""
929
930 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400931 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000932 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700933
934 if len(tokens) == 3:
935 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
936 return {}
937
Tianjie Xu9afb2212020-05-10 21:48:15 +0000938 import_path = tokens[1]
939 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
940 raise ValueError('Unrecognized import path {}'.format(line))
941
942 # We only recognize a subset of import statement that the init process
943 # supports. And we can loose the restriction based on how the dynamic
944 # fingerprint is used in practice. The placeholder format should be
945 # ${placeholder}, and its value should be provided by the caller through
946 # the placeholder_values.
947 for prop, value in self.placeholder_values.items():
948 prop_place_holder = '${{{}}}'.format(prop)
949 if prop_place_holder in import_path:
950 import_path = import_path.replace(prop_place_holder, value)
951 if '$' in import_path:
952 logger.info('Unresolved place holder in import path %s', import_path)
953 return {}
954
955 import_path = import_path.replace('/{}'.format(self.partition),
956 self.partition.upper())
957 logger.info('Parsing build props override from %s', import_path)
958
959 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
960 d = LoadDictionaryFromLines(lines)
961 return {key: val for key, val in d.items()
962 if key in self.props_allow_override}
963
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000964 def GetProp(self, prop):
965 return self.build_props.get(prop)
966
967
Tianjie Xucfa86222016-03-07 16:31:19 -0800968def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
969 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700970 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700971 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700972 self.mount_point = mount_point
973 self.fs_type = fs_type
974 self.device = device
975 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700976 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700977 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700978
979 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800980 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700981 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700982 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700983 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700984
Tao Baod1de6f32017-03-01 16:38:48 -0800985 assert fstab_version == 2
986
987 d = {}
988 for line in data.split("\n"):
989 line = line.strip()
990 if not line or line.startswith("#"):
991 continue
992
993 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
994 pieces = line.split()
995 if len(pieces) != 5:
996 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
997
998 # Ignore entries that are managed by vold.
999 options = pieces[4]
1000 if "voldmanaged=" in options:
1001 continue
1002
1003 # It's a good line, parse it.
1004 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001005 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001006 options = options.split(",")
1007 for i in options:
1008 if i.startswith("length="):
1009 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001010 elif i == "slotselect":
1011 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001012 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001013 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001014 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001015
Tao Baod1de6f32017-03-01 16:38:48 -08001016 mount_flags = pieces[3]
1017 # Honor the SELinux context if present.
1018 context = None
1019 for i in mount_flags.split(","):
1020 if i.startswith("context="):
1021 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001022
Tao Baod1de6f32017-03-01 16:38:48 -08001023 mount_point = pieces[1]
1024 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001025 device=pieces[0], length=length, context=context,
1026 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001027
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001028 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001029 # system. Other areas assume system is always at "/system" so point /system
1030 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001031 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001032 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001033 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001034 return d
1035
1036
Tao Bao765668f2019-10-04 22:03:00 -07001037def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1038 """Finds the path to recovery fstab and loads its contents."""
1039 # recovery fstab is only meaningful when installing an update via recovery
1040 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001041 if info_dict.get('ab_update') == 'true' and \
1042 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001043 return None
1044
1045 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1046 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1047 # cases, since it may load the info_dict from an old build (e.g. when
1048 # generating incremental OTAs from that build).
1049 system_root_image = info_dict.get('system_root_image') == 'true'
1050 if info_dict.get('no_recovery') != 'true':
1051 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1052 if isinstance(input_file, zipfile.ZipFile):
1053 if recovery_fstab_path not in input_file.namelist():
1054 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1055 else:
1056 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1057 if not os.path.exists(path):
1058 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1059 return LoadRecoveryFSTab(
1060 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1061 system_root_image)
1062
1063 if info_dict.get('recovery_as_boot') == 'true':
1064 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1065 if isinstance(input_file, zipfile.ZipFile):
1066 if recovery_fstab_path not in input_file.namelist():
1067 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1068 else:
1069 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1070 if not os.path.exists(path):
1071 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1072 return LoadRecoveryFSTab(
1073 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1074 system_root_image)
1075
1076 return None
1077
1078
Doug Zongker37974732010-09-16 17:44:38 -07001079def DumpInfoDict(d):
1080 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001081 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001082
Dan Albert8b72aef2015-03-23 19:13:21 -07001083
Daniel Norman55417142019-11-25 16:04:36 -08001084def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 """Merges dynamic partition info variables.
1086
1087 Args:
1088 framework_dict: The dictionary of dynamic partition info variables from the
1089 partial framework target files.
1090 vendor_dict: The dictionary of dynamic partition info variables from the
1091 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001092
1093 Returns:
1094 The merged dynamic partition info dictionary.
1095 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001096
1097 def uniq_concat(a, b):
1098 combined = set(a.split(" "))
1099 combined.update(set(b.split(" ")))
1100 combined = [item.strip() for item in combined if item.strip()]
1101 return " ".join(sorted(combined))
1102
1103 if (framework_dict.get("use_dynamic_partitions") !=
1104 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1105 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1106
1107 merged_dict = {"use_dynamic_partitions": "true"}
1108
1109 merged_dict["dynamic_partition_list"] = uniq_concat(
1110 framework_dict.get("dynamic_partition_list", ""),
1111 vendor_dict.get("dynamic_partition_list", ""))
1112
1113 # Super block devices are defined by the vendor dict.
1114 if "super_block_devices" in vendor_dict:
1115 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1116 for block_device in merged_dict["super_block_devices"].split(" "):
1117 key = "super_%s_device_size" % block_device
1118 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
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001122 # Partition groups and group sizes are defined by the vendor dict because
1123 # these values may vary for each board that uses a shared system image.
1124 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001125 for partition_group in merged_dict["super_partition_groups"].split(" "):
1126 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001127 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001128 if key not in vendor_dict:
1129 raise ValueError("Vendor dict does not contain required key %s." % key)
1130 merged_dict[key] = vendor_dict[key]
1131
1132 # Set the partition group's partition list using a concatenation of the
1133 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001134 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001135 merged_dict[key] = uniq_concat(
1136 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301137
Daniel Normanb0c75912020-09-24 14:30:21 -07001138 # Various other flags should be copied from the vendor dict, if defined.
1139 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1140 "super_metadata_device", "super_partition_error_limit",
1141 "super_partition_size"):
1142 if key in vendor_dict.keys():
1143 merged_dict[key] = vendor_dict[key]
1144
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001145 return merged_dict
1146
1147
Daniel Norman21c34f72020-11-11 17:25:50 -08001148def PartitionMapFromTargetFiles(target_files_dir):
1149 """Builds a map from partition -> path within an extracted target files directory."""
1150 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1151 possible_subdirs = {
1152 "system": ["SYSTEM"],
1153 "vendor": ["VENDOR", "SYSTEM/vendor"],
1154 "product": ["PRODUCT", "SYSTEM/product"],
1155 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1156 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1157 "vendor_dlkm": [
1158 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1159 ],
1160 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1161 }
1162 partition_map = {}
1163 for partition, subdirs in possible_subdirs.items():
1164 for subdir in subdirs:
1165 if os.path.exists(os.path.join(target_files_dir, subdir)):
1166 partition_map[partition] = subdir
1167 break
1168 return partition_map
1169
1170
Daniel Normand3351562020-10-29 12:33:11 -07001171def SharedUidPartitionViolations(uid_dict, partition_groups):
1172 """Checks for APK sharedUserIds that cross partition group boundaries.
1173
1174 This uses a single or merged build's shareduid_violation_modules.json
1175 output file, as generated by find_shareduid_violation.py or
1176 core/tasks/find-shareduid-violation.mk.
1177
1178 An error is defined as a sharedUserId that is found in a set of partitions
1179 that span more than one partition group.
1180
1181 Args:
1182 uid_dict: A dictionary created by using the standard json module to read a
1183 complete shareduid_violation_modules.json file.
1184 partition_groups: A list of groups, where each group is a list of
1185 partitions.
1186
1187 Returns:
1188 A list of error messages.
1189 """
1190 errors = []
1191 for uid, partitions in uid_dict.items():
1192 found_in_groups = [
1193 group for group in partition_groups
1194 if set(partitions.keys()) & set(group)
1195 ]
1196 if len(found_in_groups) > 1:
1197 errors.append(
1198 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1199 % (uid, ",".join(sorted(partitions.keys()))))
1200 return errors
1201
1202
Daniel Norman21c34f72020-11-11 17:25:50 -08001203def RunHostInitVerifier(product_out, partition_map):
1204 """Runs host_init_verifier on the init rc files within partitions.
1205
1206 host_init_verifier searches the etc/init path within each partition.
1207
1208 Args:
1209 product_out: PRODUCT_OUT directory, containing partition directories.
1210 partition_map: A map of partition name -> relative path within product_out.
1211 """
1212 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1213 cmd = ["host_init_verifier"]
1214 for partition, path in partition_map.items():
1215 if partition not in allowed_partitions:
1216 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1217 partition)
1218 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1219 # Add --property-contexts if the file exists on the partition.
1220 property_contexts = "%s_property_contexts" % (
1221 "plat" if partition == "system" else partition)
1222 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1223 property_contexts)
1224 if os.path.exists(property_contexts_path):
1225 cmd.append("--property-contexts=%s" % property_contexts_path)
1226 # Add the passwd file if the file exists on the partition.
1227 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1228 if os.path.exists(passwd_path):
1229 cmd.extend(["-p", passwd_path])
1230 return RunAndCheckOutput(cmd)
1231
1232
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001233def AppendAVBSigningArgs(cmd, partition):
1234 """Append signing arguments for avbtool."""
1235 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1236 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001237 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1238 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1239 if os.path.exists(new_key_path):
1240 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001241 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1242 if key_path and algorithm:
1243 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001244 avb_salt = OPTIONS.info_dict.get("avb_salt")
1245 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001246 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001247 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001248
1249
Tao Bao765668f2019-10-04 22:03:00 -07001250def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001251 """Returns the VBMeta arguments for partition.
1252
1253 It sets up the VBMeta argument by including the partition descriptor from the
1254 given 'image', or by configuring the partition as a chained partition.
1255
1256 Args:
1257 partition: The name of the partition (e.g. "system").
1258 image: The path to the partition image.
1259 info_dict: A dict returned by common.LoadInfoDict(). Will use
1260 OPTIONS.info_dict if None has been given.
1261
1262 Returns:
1263 A list of VBMeta arguments.
1264 """
1265 if info_dict is None:
1266 info_dict = OPTIONS.info_dict
1267
1268 # Check if chain partition is used.
1269 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001270 if not key_path:
1271 return ["--include_descriptors_from_image", image]
1272
1273 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1274 # into vbmeta.img. The recovery image will be configured on an independent
1275 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1276 # See details at
1277 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001278 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001279 return []
1280
1281 # Otherwise chain the partition into vbmeta.
1282 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1283 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001284
1285
Tao Bao02a08592018-07-22 12:40:45 -07001286def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1287 """Constructs and returns the arg to build or verify a chained partition.
1288
1289 Args:
1290 partition: The partition name.
1291 info_dict: The info dict to look up the key info and rollback index
1292 location.
1293 key: The key to be used for building or verifying the partition. Defaults to
1294 the key listed in info_dict.
1295
1296 Returns:
1297 A string of form "partition:rollback_index_location:key" that can be used to
1298 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001299 """
1300 if key is None:
1301 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001302 if key and not os.path.exists(key) and OPTIONS.search_path:
1303 new_key_path = os.path.join(OPTIONS.search_path, key)
1304 if os.path.exists(new_key_path):
1305 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001306 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001307 rollback_index_location = info_dict[
1308 "avb_" + partition + "_rollback_index_location"]
1309 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1310
1311
Tianjie20dd8f22020-04-19 15:51:16 -07001312def ConstructAftlMakeImageCommands(output_image):
1313 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001314
1315 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001316 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001317 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1318 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1319 'No AFTL manufacturer key provided.'
1320
1321 vbmeta_image = MakeTempFile()
1322 os.rename(output_image, vbmeta_image)
1323 build_info = BuildInfo(OPTIONS.info_dict)
1324 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001325 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001326 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001327 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001328 "--vbmeta_image_path", vbmeta_image,
1329 "--output", output_image,
1330 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001331 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001332 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1333 "--algorithm", "SHA256_RSA4096",
1334 "--padding", "4096"]
1335 if OPTIONS.aftl_signer_helper:
1336 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001337 return aftl_cmd
1338
1339
1340def AddAftlInclusionProof(output_image):
1341 """Appends the aftl inclusion proof to the vbmeta image."""
1342
1343 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001344 RunAndCheckOutput(aftl_cmd)
1345
1346 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1347 output_image, '--transparency_log_pub_keys',
1348 OPTIONS.aftl_key_path]
1349 RunAndCheckOutput(verify_cmd)
1350
1351
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001352def AppendGkiSigningArgs(cmd):
1353 """Append GKI signing arguments for mkbootimg."""
1354 # e.g., --gki_signing_key path/to/signing_key
1355 # --gki_signing_algorithm SHA256_RSA4096"
1356
1357 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1358 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1359 if not key_path:
1360 return
1361
1362 if not os.path.exists(key_path) and OPTIONS.search_path:
1363 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1364 if os.path.exists(new_key_path):
1365 key_path = new_key_path
1366
1367 # Checks key_path exists, before appending --gki_signing_* args.
1368 if not os.path.exists(key_path):
1369 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1370
1371 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1372 if key_path and algorithm:
1373 cmd.extend(["--gki_signing_key", key_path,
1374 "--gki_signing_algorithm", algorithm])
1375
1376 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1377 if signature_args:
1378 cmd.extend(["--gki_signing_signature_args", signature_args])
1379
1380
Daniel Norman276f0622019-07-26 14:13:51 -07001381def BuildVBMeta(image_path, partitions, name, needed_partitions):
1382 """Creates a VBMeta image.
1383
1384 It generates the requested VBMeta image. The requested image could be for
1385 top-level or chained VBMeta image, which is determined based on the name.
1386
1387 Args:
1388 image_path: The output path for the new VBMeta image.
1389 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001390 values. Only valid partition names are accepted, as partitions listed
1391 in common.AVB_PARTITIONS and custom partitions listed in
1392 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001393 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1394 needed_partitions: Partitions whose descriptors should be included into the
1395 generated VBMeta image.
1396
1397 Raises:
1398 AssertionError: On invalid input args.
1399 """
1400 avbtool = OPTIONS.info_dict["avb_avbtool"]
1401 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1402 AppendAVBSigningArgs(cmd, name)
1403
Hongguang Chenf23364d2020-04-27 18:36:36 -07001404 custom_partitions = OPTIONS.info_dict.get(
1405 "avb_custom_images_partition_list", "").strip().split()
1406
Daniel Norman276f0622019-07-26 14:13:51 -07001407 for partition, path in partitions.items():
1408 if partition not in needed_partitions:
1409 continue
1410 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001411 partition in AVB_VBMETA_PARTITIONS or
1412 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001413 'Unknown partition: {}'.format(partition)
1414 assert os.path.exists(path), \
1415 'Failed to find {} for {}'.format(path, partition)
1416 cmd.extend(GetAvbPartitionArg(partition, path))
1417
1418 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1419 if args and args.strip():
1420 split_args = shlex.split(args)
1421 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001422 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001423 # as a path relative to source tree, which may not be available at the
1424 # same location when running this script (we have the input target_files
1425 # zip only). For such cases, we additionally scan other locations (e.g.
1426 # IMAGES/, RADIO/, etc) before bailing out.
1427 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001428 chained_image = split_args[index + 1]
1429 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001430 continue
1431 found = False
1432 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1433 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001434 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001435 if os.path.exists(alt_path):
1436 split_args[index + 1] = alt_path
1437 found = True
1438 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001439 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001440 cmd.extend(split_args)
1441
1442 RunAndCheckOutput(cmd)
1443
Tianjie Xueaed60c2020-03-12 00:33:28 -07001444 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001445 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001446 AddAftlInclusionProof(image_path)
1447
Daniel Norman276f0622019-07-26 14:13:51 -07001448
J. Avila98cd4cc2020-06-10 20:09:10 +00001449def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001450 ramdisk_img = tempfile.NamedTemporaryFile()
1451
1452 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1453 cmd = ["mkbootfs", "-f", fs_config_file,
1454 os.path.join(sourcedir, "RAMDISK")]
1455 else:
1456 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1457 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001458 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001459 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001460 stdout=ramdisk_img.file.fileno())
1461 else:
1462 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001463
1464 p2.wait()
1465 p1.wait()
1466 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001467 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001468
1469 return ramdisk_img
1470
1471
Steve Muckle9793cf62020-04-08 18:27:00 -07001472def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001473 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001474 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001475
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001476 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001477 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1478 we are building a two-step special image (i.e. building a recovery image to
1479 be loaded into /boot in two-step OTAs).
1480
1481 Return the image data, or None if sourcedir does not appear to contains files
1482 for building the requested image.
1483 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001484
Yifan Hong63c5ca12020-10-08 11:54:02 -07001485 if info_dict is None:
1486 info_dict = OPTIONS.info_dict
1487
Steve Muckle9793cf62020-04-08 18:27:00 -07001488 # "boot" or "recovery", without extension.
1489 partition_name = os.path.basename(sourcedir).lower()
1490
Yifan Hong63c5ca12020-10-08 11:54:02 -07001491 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001492 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001493 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1494 logger.info("Excluded kernel binary from recovery image.")
1495 else:
1496 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001497 else:
1498 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001499 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001500 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001501 return None
1502
1503 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001504 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001505
Doug Zongkereef39442009-04-02 12:14:19 -07001506 img = tempfile.NamedTemporaryFile()
1507
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001508 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001509 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1510 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001511
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001512 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1513 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1514
Yifan Hong63c5ca12020-10-08 11:54:02 -07001515 cmd = [mkbootimg]
1516 if kernel:
1517 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001518
Benoit Fradina45a8682014-07-14 21:00:43 +02001519 fn = os.path.join(sourcedir, "second")
1520 if os.access(fn, os.F_OK):
1521 cmd.append("--second")
1522 cmd.append(fn)
1523
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001524 fn = os.path.join(sourcedir, "dtb")
1525 if os.access(fn, os.F_OK):
1526 cmd.append("--dtb")
1527 cmd.append(fn)
1528
Doug Zongker171f1cd2009-06-15 22:36:37 -07001529 fn = os.path.join(sourcedir, "cmdline")
1530 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001531 cmd.append("--cmdline")
1532 cmd.append(open(fn).read().rstrip("\n"))
1533
1534 fn = os.path.join(sourcedir, "base")
1535 if os.access(fn, os.F_OK):
1536 cmd.append("--base")
1537 cmd.append(open(fn).read().rstrip("\n"))
1538
Ying Wang4de6b5b2010-08-25 14:29:34 -07001539 fn = os.path.join(sourcedir, "pagesize")
1540 if os.access(fn, os.F_OK):
1541 cmd.append("--pagesize")
1542 cmd.append(open(fn).read().rstrip("\n"))
1543
Steve Mucklef84668e2020-03-16 19:13:46 -07001544 if partition_name == "recovery":
1545 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301546 if not args:
1547 # Fall back to "mkbootimg_args" for recovery image
1548 # in case "recovery_mkbootimg_args" is not set.
1549 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001550 else:
1551 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001552 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001553 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001554
Tao Bao76def242017-11-21 09:25:31 -08001555 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001556 if args and args.strip():
1557 cmd.extend(shlex.split(args))
1558
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001559 if has_ramdisk:
1560 cmd.extend(["--ramdisk", ramdisk_img.name])
1561
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001562 AppendGkiSigningArgs(cmd)
1563
Tao Baod95e9fd2015-03-29 23:07:41 -07001564 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001565 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001566 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001567 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001568 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001569 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001570
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001571 if partition_name == "recovery":
1572 if info_dict.get("include_recovery_dtbo") == "true":
1573 fn = os.path.join(sourcedir, "recovery_dtbo")
1574 cmd.extend(["--recovery_dtbo", fn])
1575 if info_dict.get("include_recovery_acpio") == "true":
1576 fn = os.path.join(sourcedir, "recovery_acpio")
1577 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001578
Tao Bao986ee862018-10-04 15:46:16 -07001579 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001580
Tao Bao76def242017-11-21 09:25:31 -08001581 if (info_dict.get("boot_signer") == "true" and
1582 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001583 # Hard-code the path as "/boot" for two-step special recovery image (which
1584 # will be loaded into /boot during the two-step OTA).
1585 if two_step_image:
1586 path = "/boot"
1587 else:
Tao Baobf70c312017-07-11 17:27:55 -07001588 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001589 cmd = [OPTIONS.boot_signer_path]
1590 cmd.extend(OPTIONS.boot_signer_args)
1591 cmd.extend([path, img.name,
1592 info_dict["verity_key"] + ".pk8",
1593 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001594 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001595
Tao Baod95e9fd2015-03-29 23:07:41 -07001596 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001597 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001598 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001599 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001600 # We have switched from the prebuilt futility binary to using the tool
1601 # (futility-host) built from the source. Override the setting in the old
1602 # TF.zip.
1603 futility = info_dict["futility"]
1604 if futility.startswith("prebuilts/"):
1605 futility = "futility-host"
1606 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001607 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001608 info_dict["vboot_key"] + ".vbprivk",
1609 info_dict["vboot_subkey"] + ".vbprivk",
1610 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001611 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001612 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001613
Tao Baof3282b42015-04-01 11:21:55 -07001614 # Clean up the temp files.
1615 img_unsigned.close()
1616 img_keyblock.close()
1617
David Zeuthen8fecb282017-12-01 16:24:01 -05001618 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001619 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001620 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001621 if partition_name == "recovery":
1622 part_size = info_dict["recovery_size"]
1623 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001624 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001625 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001626 "--partition_size", str(part_size), "--partition_name",
1627 partition_name]
1628 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001629 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001630 if args and args.strip():
1631 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001632 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001633
1634 img.seek(os.SEEK_SET, 0)
1635 data = img.read()
1636
1637 if has_ramdisk:
1638 ramdisk_img.close()
1639 img.close()
1640
1641 return data
1642
1643
Doug Zongkerd5131602012-08-02 14:46:42 -07001644def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001645 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001646 """Return a File object with the desired bootable image.
1647
1648 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1649 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1650 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001651
Doug Zongker55d93282011-01-25 17:03:34 -08001652 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1653 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001654 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001655 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001656
1657 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1658 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001659 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001660 return File.FromLocalFile(name, prebuilt_path)
1661
Tao Bao32fcdab2018-10-12 10:30:39 -07001662 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001663
1664 if info_dict is None:
1665 info_dict = OPTIONS.info_dict
1666
1667 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001668 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1669 # for recovery.
1670 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1671 prebuilt_name != "boot.img" or
1672 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001673
Doug Zongker6f1d0312014-08-22 08:07:12 -07001674 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001675 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001676 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001677 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001678 if data:
1679 return File(name, data)
1680 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001681
Doug Zongkereef39442009-04-02 12:14:19 -07001682
Steve Mucklee1b10862019-07-10 10:49:37 -07001683def _BuildVendorBootImage(sourcedir, info_dict=None):
1684 """Build a vendor boot image from the specified sourcedir.
1685
1686 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1687 turn them into a vendor boot image.
1688
1689 Return the image data, or None if sourcedir does not appear to contains files
1690 for building the requested image.
1691 """
1692
1693 if info_dict is None:
1694 info_dict = OPTIONS.info_dict
1695
1696 img = tempfile.NamedTemporaryFile()
1697
J. Avila98cd4cc2020-06-10 20:09:10 +00001698 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1699 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001700
1701 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1702 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1703
1704 cmd = [mkbootimg]
1705
1706 fn = os.path.join(sourcedir, "dtb")
1707 if os.access(fn, os.F_OK):
1708 cmd.append("--dtb")
1709 cmd.append(fn)
1710
1711 fn = os.path.join(sourcedir, "vendor_cmdline")
1712 if os.access(fn, os.F_OK):
1713 cmd.append("--vendor_cmdline")
1714 cmd.append(open(fn).read().rstrip("\n"))
1715
1716 fn = os.path.join(sourcedir, "base")
1717 if os.access(fn, os.F_OK):
1718 cmd.append("--base")
1719 cmd.append(open(fn).read().rstrip("\n"))
1720
1721 fn = os.path.join(sourcedir, "pagesize")
1722 if os.access(fn, os.F_OK):
1723 cmd.append("--pagesize")
1724 cmd.append(open(fn).read().rstrip("\n"))
1725
1726 args = info_dict.get("mkbootimg_args")
1727 if args and args.strip():
1728 cmd.extend(shlex.split(args))
1729
1730 args = info_dict.get("mkbootimg_version_args")
1731 if args and args.strip():
1732 cmd.extend(shlex.split(args))
1733
1734 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1735 cmd.extend(["--vendor_boot", img.name])
1736
Devin Moore50509012021-01-13 10:45:04 -08001737 fn = os.path.join(sourcedir, "vendor_bootconfig")
1738 if os.access(fn, os.F_OK):
1739 cmd.append("--vendor_bootconfig")
1740 cmd.append(fn)
1741
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001742 ramdisk_fragment_imgs = []
1743 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1744 if os.access(fn, os.F_OK):
1745 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1746 for ramdisk_fragment in ramdisk_fragments:
1747 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1748 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1749 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1750 # Use prebuilt image if found, else create ramdisk from supplied files.
1751 if os.access(fn, os.F_OK):
1752 ramdisk_fragment_pathname = fn
1753 else:
1754 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1755 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1756 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1757 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1758 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1759
Steve Mucklee1b10862019-07-10 10:49:37 -07001760 RunAndCheckOutput(cmd)
1761
1762 # AVB: if enabled, calculate and add hash.
1763 if info_dict.get("avb_enable") == "true":
1764 avbtool = info_dict["avb_avbtool"]
1765 part_size = info_dict["vendor_boot_size"]
1766 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001767 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001768 AppendAVBSigningArgs(cmd, "vendor_boot")
1769 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1770 if args and args.strip():
1771 cmd.extend(shlex.split(args))
1772 RunAndCheckOutput(cmd)
1773
1774 img.seek(os.SEEK_SET, 0)
1775 data = img.read()
1776
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001777 for f in ramdisk_fragment_imgs:
1778 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001779 ramdisk_img.close()
1780 img.close()
1781
1782 return data
1783
1784
1785def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1786 info_dict=None):
1787 """Return a File object with the desired vendor boot image.
1788
1789 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1790 the source files in 'unpack_dir'/'tree_subdir'."""
1791
1792 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1793 if os.path.exists(prebuilt_path):
1794 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1795 return File.FromLocalFile(name, prebuilt_path)
1796
1797 logger.info("building image from target_files %s...", tree_subdir)
1798
1799 if info_dict is None:
1800 info_dict = OPTIONS.info_dict
1801
Kelvin Zhang0876c412020-06-23 15:06:58 -04001802 data = _BuildVendorBootImage(
1803 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001804 if data:
1805 return File(name, data)
1806 return None
1807
1808
Narayan Kamatha07bf042017-08-14 14:49:21 +01001809def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001810 """Gunzips the given gzip compressed file to a given output file."""
1811 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001812 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001813 shutil.copyfileobj(in_file, out_file)
1814
1815
Tao Bao0ff15de2019-03-20 11:26:06 -07001816def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001817 """Unzips the archive to the given directory.
1818
1819 Args:
1820 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001821 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001822 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1823 archvie. Non-matching patterns will be filtered out. If there's no match
1824 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001825 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001826 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001827 if patterns is not None:
1828 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001829 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001830 names = input_zip.namelist()
1831 filtered = [
1832 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1833
1834 # There isn't any matching files. Don't unzip anything.
1835 if not filtered:
1836 return
1837 cmd.extend(filtered)
1838
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001839 RunAndCheckOutput(cmd)
1840
1841
Doug Zongker75f17362009-12-08 13:46:44 -08001842def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001843 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001844
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001845 Args:
1846 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1847 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1848
1849 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1850 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001851
Tao Bao1c830bf2017-12-25 10:43:47 -08001852 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001853 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001854 """
Doug Zongkereef39442009-04-02 12:14:19 -07001855
Tao Bao1c830bf2017-12-25 10:43:47 -08001856 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001857 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1858 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001859 UnzipToDir(m.group(1), tmp, pattern)
1860 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001861 filename = m.group(1)
1862 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001863 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001864
Tao Baodba59ee2018-01-09 13:21:02 -08001865 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001866
1867
Yifan Hong8a66a712019-04-04 15:37:57 -07001868def GetUserImage(which, tmpdir, input_zip,
1869 info_dict=None,
1870 allow_shared_blocks=None,
1871 hashtree_info_generator=None,
1872 reset_file_map=False):
1873 """Returns an Image object suitable for passing to BlockImageDiff.
1874
1875 This function loads the specified image from the given path. If the specified
1876 image is sparse, it also performs additional processing for OTA purpose. For
1877 example, it always adds block 0 to clobbered blocks list. It also detects
1878 files that cannot be reconstructed from the block list, for whom we should
1879 avoid applying imgdiff.
1880
1881 Args:
1882 which: The partition name.
1883 tmpdir: The directory that contains the prebuilt image and block map file.
1884 input_zip: The target-files ZIP archive.
1885 info_dict: The dict to be looked up for relevant info.
1886 allow_shared_blocks: If image is sparse, whether having shared blocks is
1887 allowed. If none, it is looked up from info_dict.
1888 hashtree_info_generator: If present and image is sparse, generates the
1889 hashtree_info for this sparse image.
1890 reset_file_map: If true and image is sparse, reset file map before returning
1891 the image.
1892 Returns:
1893 A Image object. If it is a sparse image and reset_file_map is False, the
1894 image will have file_map info loaded.
1895 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001896 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001897 info_dict = LoadInfoDict(input_zip)
1898
1899 is_sparse = info_dict.get("extfs_sparse_flag")
1900
1901 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1902 # shared blocks (i.e. some blocks will show up in multiple files' block
1903 # list). We can only allocate such shared blocks to the first "owner", and
1904 # disable imgdiff for all later occurrences.
1905 if allow_shared_blocks is None:
1906 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1907
1908 if is_sparse:
1909 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1910 hashtree_info_generator)
1911 if reset_file_map:
1912 img.ResetFileMap()
1913 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001914 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001915
1916
1917def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1918 """Returns a Image object suitable for passing to BlockImageDiff.
1919
1920 This function loads the specified non-sparse image from the given path.
1921
1922 Args:
1923 which: The partition name.
1924 tmpdir: The directory that contains the prebuilt image and block map file.
1925 Returns:
1926 A Image object.
1927 """
1928 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1929 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1930
1931 # The image and map files must have been created prior to calling
1932 # ota_from_target_files.py (since LMP).
1933 assert os.path.exists(path) and os.path.exists(mappath)
1934
Tianjie Xu41976c72019-07-03 13:57:01 -07001935 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1936
Yifan Hong8a66a712019-04-04 15:37:57 -07001937
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001938def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1939 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001940 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1941
1942 This function loads the specified sparse image from the given path, and
1943 performs additional processing for OTA purpose. For example, it always adds
1944 block 0 to clobbered blocks list. It also detects files that cannot be
1945 reconstructed from the block list, for whom we should avoid applying imgdiff.
1946
1947 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001948 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001949 tmpdir: The directory that contains the prebuilt image and block map file.
1950 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001951 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001952 hashtree_info_generator: If present, generates the hashtree_info for this
1953 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001954 Returns:
1955 A SparseImage object, with file_map info loaded.
1956 """
Tao Baoc765cca2018-01-31 17:32:40 -08001957 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1958 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1959
1960 # The image and map files must have been created prior to calling
1961 # ota_from_target_files.py (since LMP).
1962 assert os.path.exists(path) and os.path.exists(mappath)
1963
1964 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1965 # it to clobbered_blocks so that it will be written to the target
1966 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1967 clobbered_blocks = "0"
1968
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001969 image = sparse_img.SparseImage(
1970 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1971 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001972
1973 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1974 # if they contain all zeros. We can't reconstruct such a file from its block
1975 # list. Tag such entries accordingly. (Bug: 65213616)
1976 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001977 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001978 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001979 continue
1980
Tom Cherryd14b8952018-08-09 14:26:00 -07001981 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1982 # filename listed in system.map may contain an additional leading slash
1983 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1984 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001985 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001986 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001987 arcname = entry.lstrip('/')
1988 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001989 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08001990 else:
1991 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07001992
1993 assert arcname in input_zip.namelist(), \
1994 "Failed to find the ZIP entry for {}".format(entry)
1995
Tao Baoc765cca2018-01-31 17:32:40 -08001996 info = input_zip.getinfo(arcname)
1997 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001998
1999 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002000 # image, check the original block list to determine its completeness. Note
2001 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002002 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002003 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002004
Tao Baoc765cca2018-01-31 17:32:40 -08002005 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2006 ranges.extra['incomplete'] = True
2007
2008 return image
2009
2010
Doug Zongkereef39442009-04-02 12:14:19 -07002011def GetKeyPasswords(keylist):
2012 """Given a list of keys, prompt the user to enter passwords for
2013 those which require them. Return a {key: password} dict. password
2014 will be None if the key has no password."""
2015
Doug Zongker8ce7c252009-05-22 13:34:54 -07002016 no_passwords = []
2017 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002018 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002019 devnull = open("/dev/null", "w+b")
2020 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002021 # We don't need a password for things that aren't really keys.
2022 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002023 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002024 continue
2025
T.R. Fullhart37e10522013-03-18 10:31:26 -07002026 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002027 "-inform", "DER", "-nocrypt"],
2028 stdin=devnull.fileno(),
2029 stdout=devnull.fileno(),
2030 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002031 p.communicate()
2032 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002033 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002034 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002035 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002036 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2037 "-inform", "DER", "-passin", "pass:"],
2038 stdin=devnull.fileno(),
2039 stdout=devnull.fileno(),
2040 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002041 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002042 if p.returncode == 0:
2043 # Encrypted key with empty string as password.
2044 key_passwords[k] = ''
2045 elif stderr.startswith('Error decrypting key'):
2046 # Definitely encrypted key.
2047 # It would have said "Error reading key" if it didn't parse correctly.
2048 need_passwords.append(k)
2049 else:
2050 # Potentially, a type of key that openssl doesn't understand.
2051 # We'll let the routines in signapk.jar handle it.
2052 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002053 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002054
T.R. Fullhart37e10522013-03-18 10:31:26 -07002055 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002056 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002057 return key_passwords
2058
2059
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002060def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002061 """Gets the minSdkVersion declared in the APK.
2062
changho.shin0f125362019-07-08 10:59:00 +09002063 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002064 This can be both a decimal number (API Level) or a codename.
2065
2066 Args:
2067 apk_name: The APK filename.
2068
2069 Returns:
2070 The parsed SDK version string.
2071
2072 Raises:
2073 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002074 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002075 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002076 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002077 stderr=subprocess.PIPE)
2078 stdoutdata, stderrdata = proc.communicate()
2079 if proc.returncode != 0:
2080 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002081 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002082 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002083
Tao Baof47bf0f2018-03-21 23:28:51 -07002084 for line in stdoutdata.split("\n"):
2085 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002086 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2087 if m:
2088 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002089 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002090
2091
2092def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002093 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002094
Tao Baof47bf0f2018-03-21 23:28:51 -07002095 If minSdkVersion is set to a codename, it is translated to a number using the
2096 provided map.
2097
2098 Args:
2099 apk_name: The APK filename.
2100
2101 Returns:
2102 The parsed SDK version number.
2103
2104 Raises:
2105 ExternalError: On failing to get the min SDK version number.
2106 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002107 version = GetMinSdkVersion(apk_name)
2108 try:
2109 return int(version)
2110 except ValueError:
2111 # Not a decimal number. Codename?
2112 if version in codename_to_api_level_map:
2113 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002114 raise ExternalError(
2115 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2116 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002117
2118
2119def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002120 codename_to_api_level_map=None, whole_file=False,
2121 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002122 """Sign the input_name zip/jar/apk, producing output_name. Use the
2123 given key and password (the latter may be None if the key does not
2124 have a password.
2125
Doug Zongker951495f2009-08-14 12:44:19 -07002126 If whole_file is true, use the "-w" option to SignApk to embed a
2127 signature that covers the whole file in the archive comment of the
2128 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002129
2130 min_api_level is the API Level (int) of the oldest platform this file may end
2131 up on. If not specified for an APK, the API Level is obtained by interpreting
2132 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2133
2134 codename_to_api_level_map is needed to translate the codename which may be
2135 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002136
2137 Caller may optionally specify extra args to be passed to SignApk, which
2138 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002139 """
Tao Bao76def242017-11-21 09:25:31 -08002140 if codename_to_api_level_map is None:
2141 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002142 if extra_signapk_args is None:
2143 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002144
Alex Klyubin9667b182015-12-10 13:38:50 -08002145 java_library_path = os.path.join(
2146 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2147
Tao Baoe95540e2016-11-08 12:08:53 -08002148 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2149 ["-Djava.library.path=" + java_library_path,
2150 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002151 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002152 if whole_file:
2153 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002154
2155 min_sdk_version = min_api_level
2156 if min_sdk_version is None:
2157 if not whole_file:
2158 min_sdk_version = GetMinSdkVersionInt(
2159 input_name, codename_to_api_level_map)
2160 if min_sdk_version is not None:
2161 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2162
T.R. Fullhart37e10522013-03-18 10:31:26 -07002163 cmd.extend([key + OPTIONS.public_key_suffix,
2164 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002165 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002166
Tao Bao73dd4f42018-10-04 16:25:33 -07002167 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002168 if password is not None:
2169 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002170 stdoutdata, _ = proc.communicate(password)
2171 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002172 raise ExternalError(
2173 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002174 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002175
Doug Zongkereef39442009-04-02 12:14:19 -07002176
Doug Zongker37974732010-09-16 17:44:38 -07002177def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002178 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002179
Tao Bao9dd909e2017-11-14 11:27:32 -08002180 For non-AVB images, raise exception if the data is too big. Print a warning
2181 if the data is nearing the maximum size.
2182
2183 For AVB images, the actual image size should be identical to the limit.
2184
2185 Args:
2186 data: A string that contains all the data for the partition.
2187 target: The partition name. The ".img" suffix is optional.
2188 info_dict: The dict to be looked up for relevant info.
2189 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002190 if target.endswith(".img"):
2191 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002192 mount_point = "/" + target
2193
Ying Wangf8824af2014-06-03 14:07:27 -07002194 fs_type = None
2195 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002196 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002197 if mount_point == "/userdata":
2198 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002199 p = info_dict["fstab"][mount_point]
2200 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002201 device = p.device
2202 if "/" in device:
2203 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002204 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 if not fs_type or not limit:
2206 return
Doug Zongkereef39442009-04-02 12:14:19 -07002207
Andrew Boie0f9aec82012-02-14 09:32:52 -08002208 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002209 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2210 # path.
2211 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2212 if size != limit:
2213 raise ExternalError(
2214 "Mismatching image size for %s: expected %d actual %d" % (
2215 target, limit, size))
2216 else:
2217 pct = float(size) * 100.0 / limit
2218 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2219 if pct >= 99.0:
2220 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002221
2222 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002223 logger.warning("\n WARNING: %s\n", msg)
2224 else:
2225 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002226
2227
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002228def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002229 """Parses the APK certs info from a given target-files zip.
2230
2231 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2232 tuple with the following elements: (1) a dictionary that maps packages to
2233 certs (based on the "certificate" and "private_key" attributes in the file;
2234 (2) a string representing the extension of compressed APKs in the target files
2235 (e.g ".gz", ".bro").
2236
2237 Args:
2238 tf_zip: The input target_files ZipFile (already open).
2239
2240 Returns:
2241 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2242 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2243 no compressed APKs.
2244 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002245 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002246 compressed_extension = None
2247
Tao Bao0f990332017-09-08 19:02:54 -07002248 # META/apkcerts.txt contains the info for _all_ the packages known at build
2249 # time. Filter out the ones that are not installed.
2250 installed_files = set()
2251 for name in tf_zip.namelist():
2252 basename = os.path.basename(name)
2253 if basename:
2254 installed_files.add(basename)
2255
Tao Baoda30cfa2017-12-01 16:19:46 -08002256 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002257 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002258 if not line:
2259 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002260 m = re.match(
2261 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002262 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2263 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002264 line)
2265 if not m:
2266 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002267
Tao Bao818ddf52018-01-05 11:17:34 -08002268 matches = m.groupdict()
2269 cert = matches["CERT"]
2270 privkey = matches["PRIVKEY"]
2271 name = matches["NAME"]
2272 this_compressed_extension = matches["COMPRESSED"]
2273
2274 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2275 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2276 if cert in SPECIAL_CERT_STRINGS and not privkey:
2277 certmap[name] = cert
2278 elif (cert.endswith(OPTIONS.public_key_suffix) and
2279 privkey.endswith(OPTIONS.private_key_suffix) and
2280 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2281 certmap[name] = cert[:-public_key_suffix_len]
2282 else:
2283 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2284
2285 if not this_compressed_extension:
2286 continue
2287
2288 # Only count the installed files.
2289 filename = name + '.' + this_compressed_extension
2290 if filename not in installed_files:
2291 continue
2292
2293 # Make sure that all the values in the compression map have the same
2294 # extension. We don't support multiple compression methods in the same
2295 # system image.
2296 if compressed_extension:
2297 if this_compressed_extension != compressed_extension:
2298 raise ValueError(
2299 "Multiple compressed extensions: {} vs {}".format(
2300 compressed_extension, this_compressed_extension))
2301 else:
2302 compressed_extension = this_compressed_extension
2303
2304 return (certmap,
2305 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002306
2307
Doug Zongkereef39442009-04-02 12:14:19 -07002308COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002309Global options
2310
2311 -p (--path) <dir>
2312 Prepend <dir>/bin to the list of places to search for binaries run by this
2313 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002314
Doug Zongker05d3dea2009-06-22 11:32:31 -07002315 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002316 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002317
Tao Bao30df8b42018-04-23 15:32:53 -07002318 -x (--extra) <key=value>
2319 Add a key/value pair to the 'extras' dict, which device-specific extension
2320 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002321
Doug Zongkereef39442009-04-02 12:14:19 -07002322 -v (--verbose)
2323 Show command lines being executed.
2324
2325 -h (--help)
2326 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002327
2328 --logfile <file>
2329 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002330"""
2331
Kelvin Zhang0876c412020-06-23 15:06:58 -04002332
Doug Zongkereef39442009-04-02 12:14:19 -07002333def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002334 print(docstring.rstrip("\n"))
2335 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002336
2337
2338def ParseOptions(argv,
2339 docstring,
2340 extra_opts="", extra_long_opts=(),
2341 extra_option_handler=None):
2342 """Parse the options in argv and return any arguments that aren't
2343 flags. docstring is the calling module's docstring, to be displayed
2344 for errors and -h. extra_opts and extra_long_opts are for flags
2345 defined by the caller, which are processed by passing them to
2346 extra_option_handler."""
2347
2348 try:
2349 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002350 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002351 ["help", "verbose", "path=", "signapk_path=",
2352 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002353 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002354 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2355 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002356 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2357 "aftl_key_path=", "aftl_manufacturer_key_path=",
2358 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002359 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002360 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002361 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002362 sys.exit(2)
2363
Doug Zongkereef39442009-04-02 12:14:19 -07002364 for o, a in opts:
2365 if o in ("-h", "--help"):
2366 Usage(docstring)
2367 sys.exit()
2368 elif o in ("-v", "--verbose"):
2369 OPTIONS.verbose = True
2370 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002371 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002372 elif o in ("--signapk_path",):
2373 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002374 elif o in ("--signapk_shared_library_path",):
2375 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002376 elif o in ("--extra_signapk_args",):
2377 OPTIONS.extra_signapk_args = shlex.split(a)
2378 elif o in ("--java_path",):
2379 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002380 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002381 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002382 elif o in ("--android_jar_path",):
2383 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002384 elif o in ("--public_key_suffix",):
2385 OPTIONS.public_key_suffix = a
2386 elif o in ("--private_key_suffix",):
2387 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002388 elif o in ("--boot_signer_path",):
2389 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002390 elif o in ("--boot_signer_args",):
2391 OPTIONS.boot_signer_args = shlex.split(a)
2392 elif o in ("--verity_signer_path",):
2393 OPTIONS.verity_signer_path = a
2394 elif o in ("--verity_signer_args",):
2395 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002396 elif o in ("--aftl_tool_path",):
2397 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002398 elif o in ("--aftl_server",):
2399 OPTIONS.aftl_server = a
2400 elif o in ("--aftl_key_path",):
2401 OPTIONS.aftl_key_path = a
2402 elif o in ("--aftl_manufacturer_key_path",):
2403 OPTIONS.aftl_manufacturer_key_path = a
2404 elif o in ("--aftl_signer_helper",):
2405 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002406 elif o in ("-s", "--device_specific"):
2407 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002408 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002409 key, value = a.split("=", 1)
2410 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002411 elif o in ("--logfile",):
2412 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002413 else:
2414 if extra_option_handler is None or not extra_option_handler(o, a):
2415 assert False, "unknown option \"%s\"" % (o,)
2416
Doug Zongker85448772014-09-09 14:59:20 -07002417 if OPTIONS.search_path:
2418 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2419 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002420
2421 return args
2422
2423
Tao Bao4c851b12016-09-19 13:54:38 -07002424def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002425 """Make a temp file and add it to the list of things to be deleted
2426 when Cleanup() is called. Return the filename."""
2427 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2428 os.close(fd)
2429 OPTIONS.tempfiles.append(fn)
2430 return fn
2431
2432
Tao Bao1c830bf2017-12-25 10:43:47 -08002433def MakeTempDir(prefix='tmp', suffix=''):
2434 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2435
2436 Returns:
2437 The absolute pathname of the new directory.
2438 """
2439 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2440 OPTIONS.tempfiles.append(dir_name)
2441 return dir_name
2442
2443
Doug Zongkereef39442009-04-02 12:14:19 -07002444def Cleanup():
2445 for i in OPTIONS.tempfiles:
2446 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002447 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002448 else:
2449 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002450 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002451
2452
2453class PasswordManager(object):
2454 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002455 self.editor = os.getenv("EDITOR")
2456 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002457
2458 def GetPasswords(self, items):
2459 """Get passwords corresponding to each string in 'items',
2460 returning a dict. (The dict may have keys in addition to the
2461 values in 'items'.)
2462
2463 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2464 user edit that file to add more needed passwords. If no editor is
2465 available, or $ANDROID_PW_FILE isn't define, prompts the user
2466 interactively in the ordinary way.
2467 """
2468
2469 current = self.ReadFile()
2470
2471 first = True
2472 while True:
2473 missing = []
2474 for i in items:
2475 if i not in current or not current[i]:
2476 missing.append(i)
2477 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002478 if not missing:
2479 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002480
2481 for i in missing:
2482 current[i] = ""
2483
2484 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002485 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002486 if sys.version_info[0] >= 3:
2487 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002488 answer = raw_input("try to edit again? [y]> ").strip()
2489 if answer and answer[0] not in 'yY':
2490 raise RuntimeError("key passwords unavailable")
2491 first = False
2492
2493 current = self.UpdateAndReadFile(current)
2494
Kelvin Zhang0876c412020-06-23 15:06:58 -04002495 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002496 """Prompt the user to enter a value (password) for each key in
2497 'current' whose value is fales. Returns a new dict with all the
2498 values.
2499 """
2500 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002501 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002502 if v:
2503 result[k] = v
2504 else:
2505 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002506 result[k] = getpass.getpass(
2507 "Enter password for %s key> " % k).strip()
2508 if result[k]:
2509 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002510 return result
2511
2512 def UpdateAndReadFile(self, current):
2513 if not self.editor or not self.pwfile:
2514 return self.PromptResult(current)
2515
2516 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002517 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002518 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2519 f.write("# (Additional spaces are harmless.)\n\n")
2520
2521 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002522 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002523 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002524 f.write("[[[ %s ]]] %s\n" % (v, k))
2525 if not v and first_line is None:
2526 # position cursor on first line with no password.
2527 first_line = i + 4
2528 f.close()
2529
Tao Bao986ee862018-10-04 15:46:16 -07002530 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002531
2532 return self.ReadFile()
2533
2534 def ReadFile(self):
2535 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002536 if self.pwfile is None:
2537 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002538 try:
2539 f = open(self.pwfile, "r")
2540 for line in f:
2541 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002542 if not line or line[0] == '#':
2543 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002544 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2545 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002546 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002547 else:
2548 result[m.group(2)] = m.group(1)
2549 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002550 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002551 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002552 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002553 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002554
2555
Dan Albert8e0178d2015-01-27 15:53:15 -08002556def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2557 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002558
2559 # http://b/18015246
2560 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2561 # for files larger than 2GiB. We can work around this by adjusting their
2562 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2563 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2564 # it isn't clear to me exactly what circumstances cause this).
2565 # `zipfile.write()` must be used directly to work around this.
2566 #
2567 # This mess can be avoided if we port to python3.
2568 saved_zip64_limit = zipfile.ZIP64_LIMIT
2569 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2570
2571 if compress_type is None:
2572 compress_type = zip_file.compression
2573 if arcname is None:
2574 arcname = filename
2575
2576 saved_stat = os.stat(filename)
2577
2578 try:
2579 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2580 # file to be zipped and reset it when we're done.
2581 os.chmod(filename, perms)
2582
2583 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002584 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2585 # intentional. zip stores datetimes in local time without a time zone
2586 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2587 # in the zip archive.
2588 local_epoch = datetime.datetime.fromtimestamp(0)
2589 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002590 os.utime(filename, (timestamp, timestamp))
2591
2592 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2593 finally:
2594 os.chmod(filename, saved_stat.st_mode)
2595 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2596 zipfile.ZIP64_LIMIT = saved_zip64_limit
2597
2598
Tao Bao58c1b962015-05-20 09:32:18 -07002599def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002600 compress_type=None):
2601 """Wrap zipfile.writestr() function to work around the zip64 limit.
2602
2603 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2604 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2605 when calling crc32(bytes).
2606
2607 But it still works fine to write a shorter string into a large zip file.
2608 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2609 when we know the string won't be too long.
2610 """
2611
2612 saved_zip64_limit = zipfile.ZIP64_LIMIT
2613 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2614
2615 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2616 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002617 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002618 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002619 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002620 else:
Tao Baof3282b42015-04-01 11:21:55 -07002621 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002622 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2623 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2624 # such a case (since
2625 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2626 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2627 # permission bits. We follow the logic in Python 3 to get consistent
2628 # behavior between using the two versions.
2629 if not zinfo.external_attr:
2630 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002631
2632 # If compress_type is given, it overrides the value in zinfo.
2633 if compress_type is not None:
2634 zinfo.compress_type = compress_type
2635
Tao Bao58c1b962015-05-20 09:32:18 -07002636 # If perms is given, it has a priority.
2637 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002638 # If perms doesn't set the file type, mark it as a regular file.
2639 if perms & 0o770000 == 0:
2640 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002641 zinfo.external_attr = perms << 16
2642
Tao Baof3282b42015-04-01 11:21:55 -07002643 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002644 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2645
Dan Albert8b72aef2015-03-23 19:13:21 -07002646 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002647 zipfile.ZIP64_LIMIT = saved_zip64_limit
2648
2649
Tao Bao89d7ab22017-12-14 17:05:33 -08002650def ZipDelete(zip_filename, entries):
2651 """Deletes entries from a ZIP file.
2652
2653 Since deleting entries from a ZIP file is not supported, it shells out to
2654 'zip -d'.
2655
2656 Args:
2657 zip_filename: The name of the ZIP file.
2658 entries: The name of the entry, or the list of names to be deleted.
2659
2660 Raises:
2661 AssertionError: In case of non-zero return from 'zip'.
2662 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002663 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002664 entries = [entries]
2665 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002666 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002667
2668
Tao Baof3282b42015-04-01 11:21:55 -07002669def ZipClose(zip_file):
2670 # http://b/18015246
2671 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2672 # central directory.
2673 saved_zip64_limit = zipfile.ZIP64_LIMIT
2674 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2675
2676 zip_file.close()
2677
2678 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002679
2680
2681class DeviceSpecificParams(object):
2682 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002683
Doug Zongker05d3dea2009-06-22 11:32:31 -07002684 def __init__(self, **kwargs):
2685 """Keyword arguments to the constructor become attributes of this
2686 object, which is passed to all functions in the device-specific
2687 module."""
Tao Bao38884282019-07-10 22:20:56 -07002688 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002689 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002690 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002691
2692 if self.module is None:
2693 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002694 if not path:
2695 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002696 try:
2697 if os.path.isdir(path):
2698 info = imp.find_module("releasetools", [path])
2699 else:
2700 d, f = os.path.split(path)
2701 b, x = os.path.splitext(f)
2702 if x == ".py":
2703 f = b
2704 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002705 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002706 self.module = imp.load_module("device_specific", *info)
2707 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002708 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002709
2710 def _DoCall(self, function_name, *args, **kwargs):
2711 """Call the named function in the device-specific module, passing
2712 the given args and kwargs. The first argument to the call will be
2713 the DeviceSpecific object itself. If there is no module, or the
2714 module does not define the function, return the value of the
2715 'default' kwarg (which itself defaults to None)."""
2716 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002717 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002718 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2719
2720 def FullOTA_Assertions(self):
2721 """Called after emitting the block of assertions at the top of a
2722 full OTA package. Implementations can add whatever additional
2723 assertions they like."""
2724 return self._DoCall("FullOTA_Assertions")
2725
Doug Zongkere5ff5902012-01-17 10:55:37 -08002726 def FullOTA_InstallBegin(self):
2727 """Called at the start of full OTA installation."""
2728 return self._DoCall("FullOTA_InstallBegin")
2729
Yifan Hong10c530d2018-12-27 17:34:18 -08002730 def FullOTA_GetBlockDifferences(self):
2731 """Called during full OTA installation and verification.
2732 Implementation should return a list of BlockDifference objects describing
2733 the update on each additional partitions.
2734 """
2735 return self._DoCall("FullOTA_GetBlockDifferences")
2736
Doug Zongker05d3dea2009-06-22 11:32:31 -07002737 def FullOTA_InstallEnd(self):
2738 """Called at the end of full OTA installation; typically this is
2739 used to install the image for the device's baseband processor."""
2740 return self._DoCall("FullOTA_InstallEnd")
2741
2742 def IncrementalOTA_Assertions(self):
2743 """Called after emitting the block of assertions at the top of an
2744 incremental OTA package. Implementations can add whatever
2745 additional assertions they like."""
2746 return self._DoCall("IncrementalOTA_Assertions")
2747
Doug Zongkere5ff5902012-01-17 10:55:37 -08002748 def IncrementalOTA_VerifyBegin(self):
2749 """Called at the start of the verification phase of incremental
2750 OTA installation; additional checks can be placed here to abort
2751 the script before any changes are made."""
2752 return self._DoCall("IncrementalOTA_VerifyBegin")
2753
Doug Zongker05d3dea2009-06-22 11:32:31 -07002754 def IncrementalOTA_VerifyEnd(self):
2755 """Called at the end of the verification phase of incremental OTA
2756 installation; additional checks can be placed here to abort the
2757 script before any changes are made."""
2758 return self._DoCall("IncrementalOTA_VerifyEnd")
2759
Doug Zongkere5ff5902012-01-17 10:55:37 -08002760 def IncrementalOTA_InstallBegin(self):
2761 """Called at the start of incremental OTA installation (after
2762 verification is complete)."""
2763 return self._DoCall("IncrementalOTA_InstallBegin")
2764
Yifan Hong10c530d2018-12-27 17:34:18 -08002765 def IncrementalOTA_GetBlockDifferences(self):
2766 """Called during incremental OTA installation and verification.
2767 Implementation should return a list of BlockDifference objects describing
2768 the update on each additional partitions.
2769 """
2770 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2771
Doug Zongker05d3dea2009-06-22 11:32:31 -07002772 def IncrementalOTA_InstallEnd(self):
2773 """Called at the end of incremental OTA installation; typically
2774 this is used to install the image for the device's baseband
2775 processor."""
2776 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002777
Tao Bao9bc6bb22015-11-09 16:58:28 -08002778 def VerifyOTA_Assertions(self):
2779 return self._DoCall("VerifyOTA_Assertions")
2780
Tao Bao76def242017-11-21 09:25:31 -08002781
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002782class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002783 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002784 self.name = name
2785 self.data = data
2786 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002787 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002788 self.sha1 = sha1(data).hexdigest()
2789
2790 @classmethod
2791 def FromLocalFile(cls, name, diskname):
2792 f = open(diskname, "rb")
2793 data = f.read()
2794 f.close()
2795 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002796
2797 def WriteToTemp(self):
2798 t = tempfile.NamedTemporaryFile()
2799 t.write(self.data)
2800 t.flush()
2801 return t
2802
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002803 def WriteToDir(self, d):
2804 with open(os.path.join(d, self.name), "wb") as fp:
2805 fp.write(self.data)
2806
Geremy Condra36bd3652014-02-06 19:45:10 -08002807 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002808 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002809
Tao Bao76def242017-11-21 09:25:31 -08002810
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002811DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002812 ".gz": "imgdiff",
2813 ".zip": ["imgdiff", "-z"],
2814 ".jar": ["imgdiff", "-z"],
2815 ".apk": ["imgdiff", "-z"],
2816 ".img": "imgdiff",
2817}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002818
Tao Bao76def242017-11-21 09:25:31 -08002819
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002820class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002821 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002822 self.tf = tf
2823 self.sf = sf
2824 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002825 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002826
2827 def ComputePatch(self):
2828 """Compute the patch (as a string of data) needed to turn sf into
2829 tf. Returns the same tuple as GetPatch()."""
2830
2831 tf = self.tf
2832 sf = self.sf
2833
Doug Zongker24cd2802012-08-14 16:36:15 -07002834 if self.diff_program:
2835 diff_program = self.diff_program
2836 else:
2837 ext = os.path.splitext(tf.name)[1]
2838 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002839
2840 ttemp = tf.WriteToTemp()
2841 stemp = sf.WriteToTemp()
2842
2843 ext = os.path.splitext(tf.name)[1]
2844
2845 try:
2846 ptemp = tempfile.NamedTemporaryFile()
2847 if isinstance(diff_program, list):
2848 cmd = copy.copy(diff_program)
2849 else:
2850 cmd = [diff_program]
2851 cmd.append(stemp.name)
2852 cmd.append(ttemp.name)
2853 cmd.append(ptemp.name)
2854 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002855 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002856
Doug Zongkerf8340082014-08-05 10:39:37 -07002857 def run():
2858 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002859 if e:
2860 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002861 th = threading.Thread(target=run)
2862 th.start()
2863 th.join(timeout=300) # 5 mins
2864 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002865 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002866 p.terminate()
2867 th.join(5)
2868 if th.is_alive():
2869 p.kill()
2870 th.join()
2871
Tianjie Xua2a9f992018-01-05 15:15:54 -08002872 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002873 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002874 self.patch = None
2875 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002876 diff = ptemp.read()
2877 finally:
2878 ptemp.close()
2879 stemp.close()
2880 ttemp.close()
2881
2882 self.patch = diff
2883 return self.tf, self.sf, self.patch
2884
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002885 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002886 """Returns a tuple of (target_file, source_file, patch_data).
2887
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002888 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002889 computing the patch failed.
2890 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002891 return self.tf, self.sf, self.patch
2892
2893
2894def ComputeDifferences(diffs):
2895 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002896 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002897
2898 # Do the largest files first, to try and reduce the long-pole effect.
2899 by_size = [(i.tf.size, i) for i in diffs]
2900 by_size.sort(reverse=True)
2901 by_size = [i[1] for i in by_size]
2902
2903 lock = threading.Lock()
2904 diff_iter = iter(by_size) # accessed under lock
2905
2906 def worker():
2907 try:
2908 lock.acquire()
2909 for d in diff_iter:
2910 lock.release()
2911 start = time.time()
2912 d.ComputePatch()
2913 dur = time.time() - start
2914 lock.acquire()
2915
2916 tf, sf, patch = d.GetPatch()
2917 if sf.name == tf.name:
2918 name = tf.name
2919 else:
2920 name = "%s (%s)" % (tf.name, sf.name)
2921 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002922 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002923 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002924 logger.info(
2925 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2926 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002927 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002928 except Exception:
2929 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002930 raise
2931
2932 # start worker threads; wait for them all to finish.
2933 threads = [threading.Thread(target=worker)
2934 for i in range(OPTIONS.worker_threads)]
2935 for th in threads:
2936 th.start()
2937 while threads:
2938 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002939
2940
Dan Albert8b72aef2015-03-23 19:13:21 -07002941class BlockDifference(object):
2942 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002943 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002944 self.tgt = tgt
2945 self.src = src
2946 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002947 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002948 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002949
Tao Baodd2a5892015-03-12 12:32:37 -07002950 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002951 version = max(
2952 int(i) for i in
2953 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002954 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002955 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002956
Tianjie Xu41976c72019-07-03 13:57:01 -07002957 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2958 version=self.version,
2959 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002960 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002961 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002962 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002963 self.touched_src_ranges = b.touched_src_ranges
2964 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002965
Yifan Hong10c530d2018-12-27 17:34:18 -08002966 # On devices with dynamic partitions, for new partitions,
2967 # src is None but OPTIONS.source_info_dict is not.
2968 if OPTIONS.source_info_dict is None:
2969 is_dynamic_build = OPTIONS.info_dict.get(
2970 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002971 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002972 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002973 is_dynamic_build = OPTIONS.source_info_dict.get(
2974 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002975 is_dynamic_source = partition in shlex.split(
2976 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002977
Yifan Hongbb2658d2019-01-25 12:30:58 -08002978 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002979 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2980
Yifan Hongbb2658d2019-01-25 12:30:58 -08002981 # For dynamic partitions builds, check partition list in both source
2982 # and target build because new partitions may be added, and existing
2983 # partitions may be removed.
2984 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2985
Yifan Hong10c530d2018-12-27 17:34:18 -08002986 if is_dynamic:
2987 self.device = 'map_partition("%s")' % partition
2988 else:
2989 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002990 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2991 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002992 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002993 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2994 OPTIONS.source_info_dict)
2995 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002996
Tao Baod8d14be2016-02-04 14:26:02 -08002997 @property
2998 def required_cache(self):
2999 return self._required_cache
3000
Tao Bao76def242017-11-21 09:25:31 -08003001 def WriteScript(self, script, output_zip, progress=None,
3002 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003003 if not self.src:
3004 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003005 script.Print("Patching %s image unconditionally..." % (self.partition,))
3006 else:
3007 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003008
Dan Albert8b72aef2015-03-23 19:13:21 -07003009 if progress:
3010 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003011 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003012
3013 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003014 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003015
Tao Bao9bc6bb22015-11-09 16:58:28 -08003016 def WriteStrictVerifyScript(self, script):
3017 """Verify all the blocks in the care_map, including clobbered blocks.
3018
3019 This differs from the WriteVerifyScript() function: a) it prints different
3020 error messages; b) it doesn't allow half-way updated images to pass the
3021 verification."""
3022
3023 partition = self.partition
3024 script.Print("Verifying %s..." % (partition,))
3025 ranges = self.tgt.care_map
3026 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003027 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003028 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3029 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003030 self.device, ranges_str,
3031 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003032 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003033 script.AppendExtra("")
3034
Tao Baod522bdc2016-04-12 15:53:16 -07003035 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003036 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003037
3038 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003039 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003040 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003041
3042 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003043 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003044 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003045 ranges = self.touched_src_ranges
3046 expected_sha1 = self.touched_src_sha1
3047 else:
3048 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3049 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003050
3051 # No blocks to be checked, skipping.
3052 if not ranges:
3053 return
3054
Tao Bao5ece99d2015-05-12 11:42:31 -07003055 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003056 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003057 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003058 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3059 '"%s.patch.dat")) then' % (
3060 self.device, ranges_str, expected_sha1,
3061 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003062 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003063 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003064
Tianjie Xufc3422a2015-12-15 11:53:59 -08003065 if self.version >= 4:
3066
3067 # Bug: 21124327
3068 # When generating incrementals for the system and vendor partitions in
3069 # version 4 or newer, explicitly check the first block (which contains
3070 # the superblock) of the partition to see if it's what we expect. If
3071 # this check fails, give an explicit log message about the partition
3072 # having been remounted R/W (the most likely explanation).
3073 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003074 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003075
3076 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003077 if partition == "system":
3078 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3079 else:
3080 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003081 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003082 'ifelse (block_image_recover({device}, "{ranges}") && '
3083 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003084 'package_extract_file("{partition}.transfer.list"), '
3085 '"{partition}.new.dat", "{partition}.patch.dat"), '
3086 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003087 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003088 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003089 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003090
Tao Baodd2a5892015-03-12 12:32:37 -07003091 # Abort the OTA update. Note that the incremental OTA cannot be applied
3092 # even if it may match the checksum of the target partition.
3093 # a) If version < 3, operations like move and erase will make changes
3094 # unconditionally and damage the partition.
3095 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003096 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003097 if partition == "system":
3098 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3099 else:
3100 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3101 script.AppendExtra((
3102 'abort("E%d: %s partition has unexpected contents");\n'
3103 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003104
Yifan Hong10c530d2018-12-27 17:34:18 -08003105 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003106 partition = self.partition
3107 script.Print('Verifying the updated %s image...' % (partition,))
3108 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3109 ranges = self.tgt.care_map
3110 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003111 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003112 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003113 self.device, ranges_str,
3114 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003115
3116 # Bug: 20881595
3117 # Verify that extended blocks are really zeroed out.
3118 if self.tgt.extended:
3119 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003120 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003121 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003122 self.device, ranges_str,
3123 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003124 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003125 if partition == "system":
3126 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3127 else:
3128 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003129 script.AppendExtra(
3130 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003131 ' abort("E%d: %s partition has unexpected non-zero contents after '
3132 'OTA update");\n'
3133 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003134 else:
3135 script.Print('Verified the updated %s image.' % (partition,))
3136
Tianjie Xu209db462016-05-24 17:34:52 -07003137 if partition == "system":
3138 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3139 else:
3140 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3141
Tao Bao5fcaaef2015-06-01 13:40:49 -07003142 script.AppendExtra(
3143 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003144 ' abort("E%d: %s partition has unexpected contents after OTA '
3145 'update");\n'
3146 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003147
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003148 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003149 ZipWrite(output_zip,
3150 '{}.transfer.list'.format(self.path),
3151 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003152
Tao Bao76def242017-11-21 09:25:31 -08003153 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3154 # its size. Quailty 9 almost triples the compression time but doesn't
3155 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003156 # zip | brotli(quality 6) | brotli(quality 9)
3157 # compressed_size: 942M | 869M (~8% reduced) | 854M
3158 # compression_time: 75s | 265s | 719s
3159 # decompression_time: 15s | 25s | 25s
3160
3161 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003162 brotli_cmd = ['brotli', '--quality=6',
3163 '--output={}.new.dat.br'.format(self.path),
3164 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003165 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003166 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003167
3168 new_data_name = '{}.new.dat.br'.format(self.partition)
3169 ZipWrite(output_zip,
3170 '{}.new.dat.br'.format(self.path),
3171 new_data_name,
3172 compress_type=zipfile.ZIP_STORED)
3173 else:
3174 new_data_name = '{}.new.dat'.format(self.partition)
3175 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3176
Dan Albert8e0178d2015-01-27 15:53:15 -08003177 ZipWrite(output_zip,
3178 '{}.patch.dat'.format(self.path),
3179 '{}.patch.dat'.format(self.partition),
3180 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003181
Tianjie Xu209db462016-05-24 17:34:52 -07003182 if self.partition == "system":
3183 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3184 else:
3185 code = ErrorCode.VENDOR_UPDATE_FAILURE
3186
Yifan Hong10c530d2018-12-27 17:34:18 -08003187 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003188 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003189 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003190 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003191 device=self.device, partition=self.partition,
3192 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003193 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003194
Kelvin Zhang0876c412020-06-23 15:06:58 -04003195 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003196 data = source.ReadRangeSet(ranges)
3197 ctx = sha1()
3198
3199 for p in data:
3200 ctx.update(p)
3201
3202 return ctx.hexdigest()
3203
Kelvin Zhang0876c412020-06-23 15:06:58 -04003204 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003205 """Return the hash value for all zero blocks."""
3206 zero_block = '\x00' * 4096
3207 ctx = sha1()
3208 for _ in range(num_blocks):
3209 ctx.update(zero_block)
3210
3211 return ctx.hexdigest()
3212
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003213
Tianjie Xu41976c72019-07-03 13:57:01 -07003214# Expose these two classes to support vendor-specific scripts
3215DataImage = images.DataImage
3216EmptyImage = images.EmptyImage
3217
Tao Bao76def242017-11-21 09:25:31 -08003218
Doug Zongker96a57e72010-09-26 14:57:41 -07003219# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003220PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003221 "ext4": "EMMC",
3222 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003223 "f2fs": "EMMC",
3224 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003225}
Doug Zongker96a57e72010-09-26 14:57:41 -07003226
Kelvin Zhang0876c412020-06-23 15:06:58 -04003227
Yifan Hongbdb32012020-05-07 12:38:53 -07003228def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3229 """
3230 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3231 backwards compatibility. It aborts if the fstab entry has slotselect option
3232 (unless check_no_slot is explicitly set to False).
3233 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003234 fstab = info["fstab"]
3235 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003236 if check_no_slot:
3237 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003238 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003239 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3240 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003241 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003242
3243
Yifan Hongbdb32012020-05-07 12:38:53 -07003244def GetTypeAndDeviceExpr(mount_point, info):
3245 """
3246 Return the filesystem of the partition, and an edify expression that evaluates
3247 to the device at runtime.
3248 """
3249 fstab = info["fstab"]
3250 if fstab:
3251 p = fstab[mount_point]
3252 device_expr = '"%s"' % fstab[mount_point].device
3253 if p.slotselect:
3254 device_expr = 'add_slot_suffix(%s)' % device_expr
3255 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003256 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003257
3258
3259def GetEntryForDevice(fstab, device):
3260 """
3261 Returns:
3262 The first entry in fstab whose device is the given value.
3263 """
3264 if not fstab:
3265 return None
3266 for mount_point in fstab:
3267 if fstab[mount_point].device == device:
3268 return fstab[mount_point]
3269 return None
3270
Kelvin Zhang0876c412020-06-23 15:06:58 -04003271
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003272def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003273 """Parses and converts a PEM-encoded certificate into DER-encoded.
3274
3275 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3276
3277 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003278 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003279 """
3280 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003281 save = False
3282 for line in data.split("\n"):
3283 if "--END CERTIFICATE--" in line:
3284 break
3285 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003286 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003287 if "--BEGIN CERTIFICATE--" in line:
3288 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003289 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003290 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003291
Tao Bao04e1f012018-02-04 12:13:35 -08003292
3293def ExtractPublicKey(cert):
3294 """Extracts the public key (PEM-encoded) from the given certificate file.
3295
3296 Args:
3297 cert: The certificate filename.
3298
3299 Returns:
3300 The public key string.
3301
3302 Raises:
3303 AssertionError: On non-zero return from 'openssl'.
3304 """
3305 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3306 # While openssl 1.1 writes the key into the given filename followed by '-out',
3307 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3308 # stdout instead.
3309 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3310 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3311 pubkey, stderrdata = proc.communicate()
3312 assert proc.returncode == 0, \
3313 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3314 return pubkey
3315
3316
Tao Bao1ac886e2019-06-26 11:58:22 -07003317def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003318 """Extracts the AVB public key from the given public or private key.
3319
3320 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003321 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003322 key: The input key file, which should be PEM-encoded public or private key.
3323
3324 Returns:
3325 The path to the extracted AVB public key file.
3326 """
3327 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3328 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003329 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003330 return output
3331
3332
Doug Zongker412c02f2014-02-13 10:58:24 -08003333def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3334 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003335 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003336
Tao Bao6d5d6232018-03-09 17:04:42 -08003337 Most of the space in the boot and recovery images is just the kernel, which is
3338 identical for the two, so the resulting patch should be efficient. Add it to
3339 the output zip, along with a shell script that is run from init.rc on first
3340 boot to actually do the patching and install the new recovery image.
3341
3342 Args:
3343 input_dir: The top-level input directory of the target-files.zip.
3344 output_sink: The callback function that writes the result.
3345 recovery_img: File object for the recovery image.
3346 boot_img: File objects for the boot image.
3347 info_dict: A dict returned by common.LoadInfoDict() on the input
3348 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003349 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003350 if info_dict is None:
3351 info_dict = OPTIONS.info_dict
3352
Tao Bao6d5d6232018-03-09 17:04:42 -08003353 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003354 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3355
3356 if board_uses_vendorimage:
3357 # In this case, the output sink is rooted at VENDOR
3358 recovery_img_path = "etc/recovery.img"
3359 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3360 sh_dir = "bin"
3361 else:
3362 # In this case the output sink is rooted at SYSTEM
3363 recovery_img_path = "vendor/etc/recovery.img"
3364 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3365 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003366
Tao Baof2cffbd2015-07-22 12:33:18 -07003367 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003368 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003369
3370 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003371 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003372 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003373 # With system-root-image, boot and recovery images will have mismatching
3374 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3375 # to handle such a case.
3376 if system_root_image:
3377 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003378 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003379 assert not os.path.exists(path)
3380 else:
3381 diff_program = ["imgdiff"]
3382 if os.path.exists(path):
3383 diff_program.append("-b")
3384 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003385 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003386 else:
3387 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003388
3389 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3390 _, _, patch = d.ComputePatch()
3391 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003392
Dan Albertebb19aa2015-03-27 19:11:53 -07003393 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003394 # The following GetTypeAndDevice()s need to use the path in the target
3395 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003396 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3397 check_no_slot=False)
3398 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3399 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003400 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003401 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003402
Tao Baof2cffbd2015-07-22 12:33:18 -07003403 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003404
3405 # Note that we use /vendor to refer to the recovery resources. This will
3406 # work for a separate vendor partition mounted at /vendor or a
3407 # /system/vendor subdirectory on the system partition, for which init will
3408 # create a symlink from /vendor to /system/vendor.
3409
3410 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003411if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3412 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003413 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003414 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3415 log -t recovery "Installing new recovery image: succeeded" || \\
3416 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003417else
3418 log -t recovery "Recovery image already installed"
3419fi
3420""" % {'type': recovery_type,
3421 'device': recovery_device,
3422 'sha1': recovery_img.sha1,
3423 'size': recovery_img.size}
3424 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003425 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003426if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3427 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003428 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003429 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3430 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3431 log -t recovery "Installing new recovery image: succeeded" || \\
3432 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003433else
3434 log -t recovery "Recovery image already installed"
3435fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003436""" % {'boot_size': boot_img.size,
3437 'boot_sha1': boot_img.sha1,
3438 'recovery_size': recovery_img.size,
3439 'recovery_sha1': recovery_img.sha1,
3440 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003441 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003442 'recovery_type': recovery_type,
3443 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003444 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003445
Bill Peckhame868aec2019-09-17 17:06:47 -07003446 # The install script location moved from /system/etc to /system/bin in the L
3447 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3448 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003449
Tao Bao32fcdab2018-10-12 10:30:39 -07003450 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003451
Tao Baoda30cfa2017-12-01 16:19:46 -08003452 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003453
3454
3455class DynamicPartitionUpdate(object):
3456 def __init__(self, src_group=None, tgt_group=None, progress=None,
3457 block_difference=None):
3458 self.src_group = src_group
3459 self.tgt_group = tgt_group
3460 self.progress = progress
3461 self.block_difference = block_difference
3462
3463 @property
3464 def src_size(self):
3465 if not self.block_difference:
3466 return 0
3467 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3468
3469 @property
3470 def tgt_size(self):
3471 if not self.block_difference:
3472 return 0
3473 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3474
3475 @staticmethod
3476 def _GetSparseImageSize(img):
3477 if not img:
3478 return 0
3479 return img.blocksize * img.total_blocks
3480
3481
3482class DynamicGroupUpdate(object):
3483 def __init__(self, src_size=None, tgt_size=None):
3484 # None: group does not exist. 0: no size limits.
3485 self.src_size = src_size
3486 self.tgt_size = tgt_size
3487
3488
3489class DynamicPartitionsDifference(object):
3490 def __init__(self, info_dict, block_diffs, progress_dict=None,
3491 source_info_dict=None):
3492 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003493 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003494
3495 self._remove_all_before_apply = False
3496 if source_info_dict is None:
3497 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003498 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003499
Tao Baof1113e92019-06-18 12:10:14 -07003500 block_diff_dict = collections.OrderedDict(
3501 [(e.partition, e) for e in block_diffs])
3502
Yifan Hong10c530d2018-12-27 17:34:18 -08003503 assert len(block_diff_dict) == len(block_diffs), \
3504 "Duplicated BlockDifference object for {}".format(
3505 [partition for partition, count in
3506 collections.Counter(e.partition for e in block_diffs).items()
3507 if count > 1])
3508
Yifan Hong79997e52019-01-23 16:56:19 -08003509 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003510
3511 for p, block_diff in block_diff_dict.items():
3512 self._partition_updates[p] = DynamicPartitionUpdate()
3513 self._partition_updates[p].block_difference = block_diff
3514
3515 for p, progress in progress_dict.items():
3516 if p in self._partition_updates:
3517 self._partition_updates[p].progress = progress
3518
3519 tgt_groups = shlex.split(info_dict.get(
3520 "super_partition_groups", "").strip())
3521 src_groups = shlex.split(source_info_dict.get(
3522 "super_partition_groups", "").strip())
3523
3524 for g in tgt_groups:
3525 for p in shlex.split(info_dict.get(
3526 "super_%s_partition_list" % g, "").strip()):
3527 assert p in self._partition_updates, \
3528 "{} is in target super_{}_partition_list but no BlockDifference " \
3529 "object is provided.".format(p, g)
3530 self._partition_updates[p].tgt_group = g
3531
3532 for g in src_groups:
3533 for p in shlex.split(source_info_dict.get(
3534 "super_%s_partition_list" % g, "").strip()):
3535 assert p in self._partition_updates, \
3536 "{} is in source super_{}_partition_list but no BlockDifference " \
3537 "object is provided.".format(p, g)
3538 self._partition_updates[p].src_group = g
3539
Yifan Hong45433e42019-01-18 13:55:25 -08003540 target_dynamic_partitions = set(shlex.split(info_dict.get(
3541 "dynamic_partition_list", "").strip()))
3542 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3543 if u.tgt_size)
3544 assert block_diffs_with_target == target_dynamic_partitions, \
3545 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3546 list(target_dynamic_partitions), list(block_diffs_with_target))
3547
3548 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3549 "dynamic_partition_list", "").strip()))
3550 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3551 if u.src_size)
3552 assert block_diffs_with_source == source_dynamic_partitions, \
3553 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3554 list(source_dynamic_partitions), list(block_diffs_with_source))
3555
Yifan Hong10c530d2018-12-27 17:34:18 -08003556 if self._partition_updates:
3557 logger.info("Updating dynamic partitions %s",
3558 self._partition_updates.keys())
3559
Yifan Hong79997e52019-01-23 16:56:19 -08003560 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003561
3562 for g in tgt_groups:
3563 self._group_updates[g] = DynamicGroupUpdate()
3564 self._group_updates[g].tgt_size = int(info_dict.get(
3565 "super_%s_group_size" % g, "0").strip())
3566
3567 for g in src_groups:
3568 if g not in self._group_updates:
3569 self._group_updates[g] = DynamicGroupUpdate()
3570 self._group_updates[g].src_size = int(source_info_dict.get(
3571 "super_%s_group_size" % g, "0").strip())
3572
3573 self._Compute()
3574
3575 def WriteScript(self, script, output_zip, write_verify_script=False):
3576 script.Comment('--- Start patching dynamic partitions ---')
3577 for p, u in self._partition_updates.items():
3578 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3579 script.Comment('Patch partition %s' % p)
3580 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3581 write_verify_script=False)
3582
3583 op_list_path = MakeTempFile()
3584 with open(op_list_path, 'w') as f:
3585 for line in self._op_list:
3586 f.write('{}\n'.format(line))
3587
3588 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3589
3590 script.Comment('Update dynamic partition metadata')
3591 script.AppendExtra('assert(update_dynamic_partitions('
3592 'package_extract_file("dynamic_partitions_op_list")));')
3593
3594 if write_verify_script:
3595 for p, u in self._partition_updates.items():
3596 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3597 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003598 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003599
3600 for p, u in self._partition_updates.items():
3601 if u.tgt_size and u.src_size <= u.tgt_size:
3602 script.Comment('Patch partition %s' % p)
3603 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3604 write_verify_script=write_verify_script)
3605 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003606 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003607
3608 script.Comment('--- End patching dynamic partitions ---')
3609
3610 def _Compute(self):
3611 self._op_list = list()
3612
3613 def append(line):
3614 self._op_list.append(line)
3615
3616 def comment(line):
3617 self._op_list.append("# %s" % line)
3618
3619 if self._remove_all_before_apply:
3620 comment('Remove all existing dynamic partitions and groups before '
3621 'applying full OTA')
3622 append('remove_all_groups')
3623
3624 for p, u in self._partition_updates.items():
3625 if u.src_group and not u.tgt_group:
3626 append('remove %s' % p)
3627
3628 for p, u in self._partition_updates.items():
3629 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3630 comment('Move partition %s from %s to default' % (p, u.src_group))
3631 append('move %s default' % p)
3632
3633 for p, u in self._partition_updates.items():
3634 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3635 comment('Shrink partition %s from %d to %d' %
3636 (p, u.src_size, u.tgt_size))
3637 append('resize %s %s' % (p, u.tgt_size))
3638
3639 for g, u in self._group_updates.items():
3640 if u.src_size is not None and u.tgt_size is None:
3641 append('remove_group %s' % g)
3642 if (u.src_size is not None and u.tgt_size is not None and
3643 u.src_size > u.tgt_size):
3644 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3645 append('resize_group %s %d' % (g, u.tgt_size))
3646
3647 for g, u in self._group_updates.items():
3648 if u.src_size is None and u.tgt_size is not None:
3649 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3650 append('add_group %s %d' % (g, u.tgt_size))
3651 if (u.src_size is not None and u.tgt_size is not None and
3652 u.src_size < u.tgt_size):
3653 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3654 append('resize_group %s %d' % (g, u.tgt_size))
3655
3656 for p, u in self._partition_updates.items():
3657 if u.tgt_group and not u.src_group:
3658 comment('Add partition %s to group %s' % (p, u.tgt_group))
3659 append('add %s %s' % (p, u.tgt_group))
3660
3661 for p, u in self._partition_updates.items():
3662 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003663 comment('Grow partition %s from %d to %d' %
3664 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003665 append('resize %s %d' % (p, u.tgt_size))
3666
3667 for p, u in self._partition_updates.items():
3668 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3669 comment('Move partition %s from default to %s' %
3670 (p, u.tgt_group))
3671 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003672
3673
jiajia tangf3f842b2021-03-17 21:49:44 +08003674def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003675 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003676 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003677
3678 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003679 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003680
3681 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003682 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003683 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003684 tmp_dir = MakeTempDir('boot_', suffix='.img')
3685 try:
3686 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3687 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3688 if not os.path.isfile(ramdisk):
3689 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3690 return None
3691 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003692 if ramdisk_format == RamdiskFormat.LZ4:
3693 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3694 elif ramdisk_format == RamdiskFormat.GZ:
3695 with open(ramdisk, 'rb') as input_stream:
3696 with open(uncompressed_ramdisk, 'wb') as output_stream:
3697 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(), stdout=output_stream.fileno())
3698 p2.wait()
3699 else:
3700 logger.error('Only support lz4 or minigzip ramdisk format.')
3701 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003702
3703 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3704 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3705 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3706 # the host environment.
3707 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3708 cwd=extracted_ramdisk)
3709
Yifan Hongc65a0542021-01-07 14:21:01 -08003710 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3711 prop_file = os.path.join(extracted_ramdisk, search_path)
3712 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003713 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003714 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3715
Yifan Hong7dc51172021-01-12 11:27:39 -08003716 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003717
Yifan Hong85ac5012021-01-07 14:43:46 -08003718 except ExternalError as e:
3719 logger.warning('Unable to get boot image build props: %s', e)
3720 return None
3721
3722
3723def GetBootImageTimestamp(boot_img):
3724 """
3725 Get timestamp from ramdisk within the boot image
3726
3727 Args:
3728 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3729
3730 Return:
3731 An integer that corresponds to the timestamp of the boot image, or None
3732 if file has unknown format. Raise exception if an unexpected error has
3733 occurred.
3734 """
3735 prop_file = GetBootImageBuildProp(boot_img)
3736 if not prop_file:
3737 return None
3738
3739 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3740 if props is None:
3741 return None
3742
3743 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003744 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3745 if timestamp:
3746 return int(timestamp)
3747 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3748 return None
3749
3750 except ExternalError as e:
3751 logger.warning('Unable to get boot image timestamp: %s', e)
3752 return None