blob: c4240c485b9a517460ffe24c35d35fb0c0398a95 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hongc65a0542021-01-07 14:21:01 -0800131# See sysprop.mk. If file is moved, add new search paths here; don't remove
132# existing search paths.
133RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700134
Tianjie Xu209db462016-05-24 17:34:52 -0700135class ErrorCode(object):
136 """Define error_codes for failures that happen during the actual
137 update package installation.
138
139 Error codes 0-999 are reserved for failures before the package
140 installation (i.e. low battery, package verification failure).
141 Detailed code in 'bootable/recovery/error_code.h' """
142
143 SYSTEM_VERIFICATION_FAILURE = 1000
144 SYSTEM_UPDATE_FAILURE = 1001
145 SYSTEM_UNEXPECTED_CONTENTS = 1002
146 SYSTEM_NONZERO_CONTENTS = 1003
147 SYSTEM_RECOVER_FAILURE = 1004
148 VENDOR_VERIFICATION_FAILURE = 2000
149 VENDOR_UPDATE_FAILURE = 2001
150 VENDOR_UNEXPECTED_CONTENTS = 2002
151 VENDOR_NONZERO_CONTENTS = 2003
152 VENDOR_RECOVER_FAILURE = 2004
153 OEM_PROP_MISMATCH = 3000
154 FINGERPRINT_MISMATCH = 3001
155 THUMBPRINT_MISMATCH = 3002
156 OLDER_BUILD = 3003
157 DEVICE_MISMATCH = 3004
158 BAD_PATCH_FILE = 3005
159 INSUFFICIENT_CACHE_SPACE = 3006
160 TUNE_PARTITION_FAILURE = 3007
161 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800162
Tao Bao80921982018-03-21 21:02:19 -0700163
Dan Albert8b72aef2015-03-23 19:13:21 -0700164class ExternalError(RuntimeError):
165 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700166
167
Tao Bao32fcdab2018-10-12 10:30:39 -0700168def InitLogging():
169 DEFAULT_LOGGING_CONFIG = {
170 'version': 1,
171 'disable_existing_loggers': False,
172 'formatters': {
173 'standard': {
174 'format':
175 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
176 'datefmt': '%Y-%m-%d %H:%M:%S',
177 },
178 },
179 'handlers': {
180 'default': {
181 'class': 'logging.StreamHandler',
182 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700183 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700184 },
185 },
186 'loggers': {
187 '': {
188 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700189 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 }
192 }
193 }
194 env_config = os.getenv('LOGGING_CONFIG')
195 if env_config:
196 with open(env_config) as f:
197 config = json.load(f)
198 else:
199 config = DEFAULT_LOGGING_CONFIG
200
201 # Increase the logging level for verbose mode.
202 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700203 config = copy.deepcopy(config)
204 config['handlers']['default']['level'] = 'INFO'
205
206 if OPTIONS.logfile:
207 config = copy.deepcopy(config)
208 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400209 'class': 'logging.FileHandler',
210 'formatter': 'standard',
211 'level': 'INFO',
212 'mode': 'w',
213 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700214 }
215 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700216
217 logging.config.dictConfig(config)
218
219
Yifan Hong8e332ff2020-07-29 17:51:55 -0700220def SetHostToolLocation(tool_name, location):
221 OPTIONS.host_tools[tool_name] = location
222
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900223def FindHostToolPath(tool_name):
224 """Finds the path to the host tool.
225
226 Args:
227 tool_name: name of the tool to find
228 Returns:
229 path to the tool if found under either one of the host_tools map or under
230 the same directory as this binary is located at. If not found, tool_name
231 is returned.
232 """
233 if tool_name in OPTIONS.host_tools:
234 return OPTIONS.host_tools[tool_name]
235
236 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
237 tool_path = os.path.join(my_dir, tool_name)
238 if os.path.exists(tool_path):
239 return tool_path
240
241 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700242
Tao Bao39451582017-05-04 11:10:47 -0700243def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700244 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700245
Tao Bao73dd4f42018-10-04 16:25:33 -0700246 Args:
247 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700248 verbose: Whether the commands should be shown. Default to the global
249 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700250 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
251 stdin, etc. stdout and stderr will default to subprocess.PIPE and
252 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800253 universal_newlines will default to True, as most of the users in
254 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700255
256 Returns:
257 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700258 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 if 'stdout' not in kwargs and 'stderr' not in kwargs:
260 kwargs['stdout'] = subprocess.PIPE
261 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 if 'universal_newlines' not in kwargs:
263 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700264
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900265 if args:
266 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700267 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900268 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700269
Tao Bao32fcdab2018-10-12 10:30:39 -0700270 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400271 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700272 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700273 return subprocess.Popen(args, **kwargs)
274
275
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800276def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800277 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800278
279 Args:
280 args: The command represented as a list of strings.
281 verbose: Whether the commands should be shown. Default to the global
282 verbosity if unspecified.
283 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
284 stdin, etc. stdout and stderr will default to subprocess.PIPE and
285 subprocess.STDOUT respectively unless caller specifies any of them.
286
Bill Peckham889b0c62019-02-21 18:53:37 -0800287 Raises:
288 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800289 """
290 proc = Run(args, verbose=verbose, **kwargs)
291 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800292
293 if proc.returncode != 0:
294 raise ExternalError(
295 "Failed to run command '{}' (exit code {})".format(
296 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800297
298
Tao Bao986ee862018-10-04 15:46:16 -0700299def RunAndCheckOutput(args, verbose=None, **kwargs):
300 """Runs the given command and returns the output.
301
302 Args:
303 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700304 verbose: Whether the commands should be shown. Default to the global
305 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700306 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
307 stdin, etc. stdout and stderr will default to subprocess.PIPE and
308 subprocess.STDOUT respectively unless caller specifies any of them.
309
310 Returns:
311 The output string.
312
313 Raises:
314 ExternalError: On non-zero exit from the command.
315 """
Tao Bao986ee862018-10-04 15:46:16 -0700316 proc = Run(args, verbose=verbose, **kwargs)
317 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800318 if output is None:
319 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700320 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400321 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700322 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700323 if proc.returncode != 0:
324 raise ExternalError(
325 "Failed to run command '{}' (exit code {}):\n{}".format(
326 args, proc.returncode, output))
327 return output
328
329
Tao Baoc765cca2018-01-31 17:32:40 -0800330def RoundUpTo4K(value):
331 rounded_up = value + 4095
332 return rounded_up - (rounded_up % 4096)
333
334
Ying Wang7e6d4e42010-12-13 16:25:36 -0800335def CloseInheritedPipes():
336 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
337 before doing other work."""
338 if platform.system() != "Darwin":
339 return
340 for d in range(3, 1025):
341 try:
342 stat = os.fstat(d)
343 if stat is not None:
344 pipebit = stat[0] & 0x1000
345 if pipebit != 0:
346 os.close(d)
347 except OSError:
348 pass
349
350
Tao Bao1c320f82019-10-04 23:25:12 -0700351class BuildInfo(object):
352 """A class that holds the information for a given build.
353
354 This class wraps up the property querying for a given source or target build.
355 It abstracts away the logic of handling OEM-specific properties, and caches
356 the commonly used properties such as fingerprint.
357
358 There are two types of info dicts: a) build-time info dict, which is generated
359 at build time (i.e. included in a target_files zip); b) OEM info dict that is
360 specified at package generation time (via command line argument
361 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
362 having "oem_fingerprint_properties" in build-time info dict), all the queries
363 would be answered based on build-time info dict only. Otherwise if using
364 OEM-specific properties, some of them will be calculated from two info dicts.
365
366 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800367 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700368
369 Attributes:
370 info_dict: The build-time info dict.
371 is_ab: Whether it's a build that uses A/B OTA.
372 oem_dicts: A list of OEM dicts.
373 oem_props: A list of OEM properties that should be read from OEM dicts; None
374 if the build doesn't use any OEM-specific property.
375 fingerprint: The fingerprint of the build, which would be calculated based
376 on OEM properties if applicable.
377 device: The device name, which could come from OEM dicts if applicable.
378 """
379
380 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
381 "ro.product.manufacturer", "ro.product.model",
382 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700383 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
384 "product", "odm", "vendor", "system_ext", "system"]
385 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
386 "product", "product_services", "odm", "vendor", "system"]
387 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Tao Bao3ed35d32019-10-07 20:48:48 -0700389 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700390 """Initializes a BuildInfo instance with the given dicts.
391
392 Note that it only wraps up the given dicts, without making copies.
393
394 Arguments:
395 info_dict: The build-time info dict.
396 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
397 that it always uses the first dict to calculate the fingerprint or the
398 device name. The rest would be used for asserting OEM properties only
399 (e.g. one package can be installed on one of these devices).
400
401 Raises:
402 ValueError: On invalid inputs.
403 """
404 self.info_dict = info_dict
405 self.oem_dicts = oem_dicts
406
407 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700408
Hongguang Chend7c160f2020-05-03 21:24:26 -0700409 # Skip _oem_props if oem_dicts is None to use BuildInfo in
410 # sign_target_files_apks
411 if self.oem_dicts:
412 self._oem_props = info_dict.get("oem_fingerprint_properties")
413 else:
414 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700415
Daniel Normand5fe8622020-01-08 17:01:11 -0800416 def check_fingerprint(fingerprint):
417 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
418 raise ValueError(
419 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
420 "3.2.2. Build Parameters.".format(fingerprint))
421
Daniel Normand5fe8622020-01-08 17:01:11 -0800422 self._partition_fingerprints = {}
423 for partition in PARTITIONS_WITH_CARE_MAP:
424 try:
425 fingerprint = self.CalculatePartitionFingerprint(partition)
426 check_fingerprint(fingerprint)
427 self._partition_fingerprints[partition] = fingerprint
428 except ExternalError:
429 continue
430 if "system" in self._partition_fingerprints:
431 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
432 # need a fingerprint when creating the image.
433 self._partition_fingerprints[
434 "system_other"] = self._partition_fingerprints["system"]
435
Tao Bao1c320f82019-10-04 23:25:12 -0700436 # These two should be computed only after setting self._oem_props.
437 self._device = self.GetOemProperty("ro.product.device")
438 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800439 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700440
441 @property
442 def is_ab(self):
443 return self._is_ab
444
445 @property
446 def device(self):
447 return self._device
448
449 @property
450 def fingerprint(self):
451 return self._fingerprint
452
453 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700454 def oem_props(self):
455 return self._oem_props
456
457 def __getitem__(self, key):
458 return self.info_dict[key]
459
460 def __setitem__(self, key, value):
461 self.info_dict[key] = value
462
463 def get(self, key, default=None):
464 return self.info_dict.get(key, default)
465
466 def items(self):
467 return self.info_dict.items()
468
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000469 def _GetRawBuildProp(self, prop, partition):
470 prop_file = '{}.build.prop'.format(
471 partition) if partition else 'build.prop'
472 partition_props = self.info_dict.get(prop_file)
473 if not partition_props:
474 return None
475 return partition_props.GetProp(prop)
476
Daniel Normand5fe8622020-01-08 17:01:11 -0800477 def GetPartitionBuildProp(self, prop, partition):
478 """Returns the inquired build property for the provided partition."""
479 # If provided a partition for this property, only look within that
480 # partition's build.prop.
481 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
482 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
483 else:
484 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000485
486 prop_val = self._GetRawBuildProp(prop, partition)
487 if prop_val is not None:
488 return prop_val
489 raise ExternalError("couldn't find %s in %s.build.prop" %
490 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800491
Tao Bao1c320f82019-10-04 23:25:12 -0700492 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800493 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700494 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
495 return self._ResolveRoProductBuildProp(prop)
496
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000497 prop_val = self._GetRawBuildProp(prop, None)
498 if prop_val is not None:
499 return prop_val
500
501 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700502
503 def _ResolveRoProductBuildProp(self, prop):
504 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000505 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700506 if prop_val:
507 return prop_val
508
Steven Laver8e2086e2020-04-27 16:26:31 -0700509 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000510 source_order_val = self._GetRawBuildProp(
511 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700512 if source_order_val:
513 source_order = source_order_val.split(",")
514 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700516
517 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700518 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700519 raise ExternalError(
520 "Invalid ro.product.property_source_order '{}'".format(source_order))
521
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000522 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700523 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000524 "ro.product", "ro.product.{}".format(source_partition), 1)
525 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700526 if prop_val:
527 return prop_val
528
529 raise ExternalError("couldn't resolve {}".format(prop))
530
Steven Laver8e2086e2020-04-27 16:26:31 -0700531 def _GetRoProductPropsDefaultSourceOrder(self):
532 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
533 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000534 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700535 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000536 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700537 if android_version == "10":
538 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
539 # NOTE: float() conversion of android_version will have rounding error.
540 # We are checking for "9" or less, and using "< 10" is well outside of
541 # possible floating point rounding.
542 try:
543 android_version_val = float(android_version)
544 except ValueError:
545 android_version_val = 0
546 if android_version_val < 10:
547 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
548 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
549
Tianjieb37c5be2020-10-15 21:27:10 -0700550 def _GetPlatformVersion(self):
551 version_sdk = self.GetBuildProp("ro.build.version.sdk")
552 # init code switches to version_release_or_codename (see b/158483506). After
553 # API finalization, release_or_codename will be the same as release. This
554 # is the best effort to support pre-S dev stage builds.
555 if int(version_sdk) >= 30:
556 try:
557 return self.GetBuildProp("ro.build.version.release_or_codename")
558 except ExternalError:
559 logger.warning('Failed to find ro.build.version.release_or_codename')
560
561 return self.GetBuildProp("ro.build.version.release")
562
563 def _GetPartitionPlatformVersion(self, partition):
564 try:
565 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
566 partition)
567 except ExternalError:
568 return self.GetPartitionBuildProp("ro.build.version.release",
569 partition)
570
Tao Bao1c320f82019-10-04 23:25:12 -0700571 def GetOemProperty(self, key):
572 if self.oem_props is not None and key in self.oem_props:
573 return self.oem_dicts[0][key]
574 return self.GetBuildProp(key)
575
Daniel Normand5fe8622020-01-08 17:01:11 -0800576 def GetPartitionFingerprint(self, partition):
577 return self._partition_fingerprints.get(partition, None)
578
579 def CalculatePartitionFingerprint(self, partition):
580 try:
581 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
582 except ExternalError:
583 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
584 self.GetPartitionBuildProp("ro.product.brand", partition),
585 self.GetPartitionBuildProp("ro.product.name", partition),
586 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700587 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800588 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400589 self.GetPartitionBuildProp(
590 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800591 self.GetPartitionBuildProp("ro.build.type", partition),
592 self.GetPartitionBuildProp("ro.build.tags", partition))
593
Tao Bao1c320f82019-10-04 23:25:12 -0700594 def CalculateFingerprint(self):
595 if self.oem_props is None:
596 try:
597 return self.GetBuildProp("ro.build.fingerprint")
598 except ExternalError:
599 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
600 self.GetBuildProp("ro.product.brand"),
601 self.GetBuildProp("ro.product.name"),
602 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700603 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700604 self.GetBuildProp("ro.build.id"),
605 self.GetBuildProp("ro.build.version.incremental"),
606 self.GetBuildProp("ro.build.type"),
607 self.GetBuildProp("ro.build.tags"))
608 return "%s/%s/%s:%s" % (
609 self.GetOemProperty("ro.product.brand"),
610 self.GetOemProperty("ro.product.name"),
611 self.GetOemProperty("ro.product.device"),
612 self.GetBuildProp("ro.build.thumbprint"))
613
614 def WriteMountOemScript(self, script):
615 assert self.oem_props is not None
616 recovery_mount_options = self.info_dict.get("recovery_mount_options")
617 script.Mount("/oem", recovery_mount_options)
618
619 def WriteDeviceAssertions(self, script, oem_no_mount):
620 # Read the property directly if not using OEM properties.
621 if not self.oem_props:
622 script.AssertDevice(self.device)
623 return
624
625 # Otherwise assert OEM properties.
626 if not self.oem_dicts:
627 raise ExternalError(
628 "No OEM file provided to answer expected assertions")
629
630 for prop in self.oem_props.split():
631 values = []
632 for oem_dict in self.oem_dicts:
633 if prop in oem_dict:
634 values.append(oem_dict[prop])
635 if not values:
636 raise ExternalError(
637 "The OEM file is missing the property %s" % (prop,))
638 script.AssertOemProperty(prop, values, oem_no_mount)
639
640
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000641def ReadFromInputFile(input_file, fn):
642 """Reads the contents of fn from input zipfile or directory."""
643 if isinstance(input_file, zipfile.ZipFile):
644 return input_file.read(fn).decode()
645 else:
646 path = os.path.join(input_file, *fn.split("/"))
647 try:
648 with open(path) as f:
649 return f.read()
650 except IOError as e:
651 if e.errno == errno.ENOENT:
652 raise KeyError(fn)
653
654
Tao Bao410ad8b2018-08-24 12:08:38 -0700655def LoadInfoDict(input_file, repacking=False):
656 """Loads the key/value pairs from the given input target_files.
657
Tianjiea85bdf02020-07-29 11:56:19 -0700658 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700659 checks and returns the parsed key/value pairs for to the given build. It's
660 usually called early when working on input target_files files, e.g. when
661 generating OTAs, or signing builds. Note that the function may be called
662 against an old target_files file (i.e. from past dessert releases). So the
663 property parsing needs to be backward compatible.
664
665 In a `META/misc_info.txt`, a few properties are stored as links to the files
666 in the PRODUCT_OUT directory. It works fine with the build system. However,
667 they are no longer available when (re)generating images from target_files zip.
668 When `repacking` is True, redirect these properties to the actual files in the
669 unzipped directory.
670
671 Args:
672 input_file: The input target_files file, which could be an open
673 zipfile.ZipFile instance, or a str for the dir that contains the files
674 unzipped from a target_files file.
675 repacking: Whether it's trying repack an target_files file after loading the
676 info dict (default: False). If so, it will rewrite a few loaded
677 properties (e.g. selinux_fc, root_dir) to point to the actual files in
678 target_files file. When doing repacking, `input_file` must be a dir.
679
680 Returns:
681 A dict that contains the parsed key/value pairs.
682
683 Raises:
684 AssertionError: On invalid input arguments.
685 ValueError: On malformed input values.
686 """
687 if repacking:
688 assert isinstance(input_file, str), \
689 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700690
Doug Zongkerc9253822014-02-04 12:17:58 -0800691 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000692 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800693
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700694 try:
Michael Runge6e836112014-04-15 17:40:21 -0700695 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700696 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700697 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700698
Tao Bao410ad8b2018-08-24 12:08:38 -0700699 if "recovery_api_version" not in d:
700 raise ValueError("Failed to find 'recovery_api_version'")
701 if "fstab_version" not in d:
702 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800703
Tao Bao410ad8b2018-08-24 12:08:38 -0700704 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700705 # "selinux_fc" properties should point to the file_contexts files
706 # (file_contexts.bin) under META/.
707 for key in d:
708 if key.endswith("selinux_fc"):
709 fc_basename = os.path.basename(d[key])
710 fc_config = os.path.join(input_file, "META", fc_basename)
711 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700712
Daniel Norman72c626f2019-05-13 15:58:14 -0700713 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700714
Tom Cherryd14b8952018-08-09 14:26:00 -0700715 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700716 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700717 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700718 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700719
David Anderson0ec64ac2019-12-06 12:21:18 -0800720 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700721 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700722 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800723 key_name = part_name + "_base_fs_file"
724 if key_name not in d:
725 continue
726 basename = os.path.basename(d[key_name])
727 base_fs_file = os.path.join(input_file, "META", basename)
728 if os.path.exists(base_fs_file):
729 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700730 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700731 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800732 "Failed to find %s base fs file: %s", part_name, base_fs_file)
733 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700734
Doug Zongker37974732010-09-16 17:44:38 -0700735 def makeint(key):
736 if key in d:
737 d[key] = int(d[key], 0)
738
739 makeint("recovery_api_version")
740 makeint("blocksize")
741 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700742 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700743 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700744 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700745 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800746 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700747
Steve Muckle903a1ca2020-05-07 17:32:10 -0700748 boot_images = "boot.img"
749 if "boot_images" in d:
750 boot_images = d["boot_images"]
751 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400752 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700753
Tao Bao765668f2019-10-04 22:03:00 -0700754 # Load recovery fstab if applicable.
755 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800756
Tianjie Xu861f4132018-09-12 11:49:33 -0700757 # Tries to load the build props for all partitions with care_map, including
758 # system and vendor.
759 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800760 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000761 d[partition_prop] = PartitionBuildProps.FromInputFile(
762 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700763 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800764
Tao Bao3ed35d32019-10-07 20:48:48 -0700765 # Set up the salt (based on fingerprint) that will be used when adding AVB
766 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800767 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700768 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800769 for partition in PARTITIONS_WITH_CARE_MAP:
770 fingerprint = build_info.GetPartitionFingerprint(partition)
771 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400772 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400773 try:
774 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
775 except KeyError:
776 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700777 return d
778
Tao Baod1de6f32017-03-01 16:38:48 -0800779
Kelvin Zhang39aea442020-08-17 11:04:25 -0400780
Daniel Norman4cc9df62019-07-18 10:11:07 -0700781def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900782 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700783 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900784
Daniel Norman4cc9df62019-07-18 10:11:07 -0700785
786def LoadDictionaryFromFile(file_path):
787 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900788 return LoadDictionaryFromLines(lines)
789
790
Michael Runge6e836112014-04-15 17:40:21 -0700791def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700792 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700793 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700794 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700795 if not line or line.startswith("#"):
796 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700797 if "=" in line:
798 name, value = line.split("=", 1)
799 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700800 return d
801
Tao Baod1de6f32017-03-01 16:38:48 -0800802
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000803class PartitionBuildProps(object):
804 """The class holds the build prop of a particular partition.
805
806 This class loads the build.prop and holds the build properties for a given
807 partition. It also partially recognizes the 'import' statement in the
808 build.prop; and calculates alternative values of some specific build
809 properties during runtime.
810
811 Attributes:
812 input_file: a zipped target-file or an unzipped target-file directory.
813 partition: name of the partition.
814 props_allow_override: a list of build properties to search for the
815 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000816 build_props: a dict of build properties for the given partition.
817 prop_overrides: a set of props that are overridden by import.
818 placeholder_values: A dict of runtime variables' values to replace the
819 placeholders in the build.prop file. We expect exactly one value for
820 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000821 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400822
Tianjie Xu9afb2212020-05-10 21:48:15 +0000823 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000824 self.input_file = input_file
825 self.partition = name
826 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000827 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000828 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000829 self.prop_overrides = set()
830 self.placeholder_values = {}
831 if placeholder_values:
832 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000833
834 @staticmethod
835 def FromDictionary(name, build_props):
836 """Constructs an instance from a build prop dictionary."""
837
838 props = PartitionBuildProps("unknown", name)
839 props.build_props = build_props.copy()
840 return props
841
842 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000843 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000844 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000845 data = ''
846 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
847 '{}/build.prop'.format(name.upper())]:
848 try:
849 data = ReadFromInputFile(input_file, prop_file)
850 break
851 except KeyError:
852 logger.warning('Failed to read %s', prop_file)
853
Tianjie Xu9afb2212020-05-10 21:48:15 +0000854 props = PartitionBuildProps(input_file, name, placeholder_values)
855 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000856 return props
857
Yifan Hong125d0b62020-09-24 17:07:03 -0700858 @staticmethod
859 def FromBuildPropFile(name, build_prop_file):
860 """Constructs an instance from a build prop file."""
861
862 props = PartitionBuildProps("unknown", name)
863 with open(build_prop_file) as f:
864 props._LoadBuildProp(f.read())
865 return props
866
Tianjie Xu9afb2212020-05-10 21:48:15 +0000867 def _LoadBuildProp(self, data):
868 for line in data.split('\n'):
869 line = line.strip()
870 if not line or line.startswith("#"):
871 continue
872 if line.startswith("import"):
873 overrides = self._ImportParser(line)
874 duplicates = self.prop_overrides.intersection(overrides.keys())
875 if duplicates:
876 raise ValueError('prop {} is overridden multiple times'.format(
877 ','.join(duplicates)))
878 self.prop_overrides = self.prop_overrides.union(overrides.keys())
879 self.build_props.update(overrides)
880 elif "=" in line:
881 name, value = line.split("=", 1)
882 if name in self.prop_overrides:
883 raise ValueError('prop {} is set again after overridden by import '
884 'statement'.format(name))
885 self.build_props[name] = value
886
887 def _ImportParser(self, line):
888 """Parses the build prop in a given import statement."""
889
890 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400891 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000892 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700893
894 if len(tokens) == 3:
895 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
896 return {}
897
Tianjie Xu9afb2212020-05-10 21:48:15 +0000898 import_path = tokens[1]
899 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
900 raise ValueError('Unrecognized import path {}'.format(line))
901
902 # We only recognize a subset of import statement that the init process
903 # supports. And we can loose the restriction based on how the dynamic
904 # fingerprint is used in practice. The placeholder format should be
905 # ${placeholder}, and its value should be provided by the caller through
906 # the placeholder_values.
907 for prop, value in self.placeholder_values.items():
908 prop_place_holder = '${{{}}}'.format(prop)
909 if prop_place_holder in import_path:
910 import_path = import_path.replace(prop_place_holder, value)
911 if '$' in import_path:
912 logger.info('Unresolved place holder in import path %s', import_path)
913 return {}
914
915 import_path = import_path.replace('/{}'.format(self.partition),
916 self.partition.upper())
917 logger.info('Parsing build props override from %s', import_path)
918
919 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
920 d = LoadDictionaryFromLines(lines)
921 return {key: val for key, val in d.items()
922 if key in self.props_allow_override}
923
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000924 def GetProp(self, prop):
925 return self.build_props.get(prop)
926
927
Tianjie Xucfa86222016-03-07 16:31:19 -0800928def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
929 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700930 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700931 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700932 self.mount_point = mount_point
933 self.fs_type = fs_type
934 self.device = device
935 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700936 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700937 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700938
939 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800940 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700941 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700942 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700943 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700944
Tao Baod1de6f32017-03-01 16:38:48 -0800945 assert fstab_version == 2
946
947 d = {}
948 for line in data.split("\n"):
949 line = line.strip()
950 if not line or line.startswith("#"):
951 continue
952
953 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
954 pieces = line.split()
955 if len(pieces) != 5:
956 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
957
958 # Ignore entries that are managed by vold.
959 options = pieces[4]
960 if "voldmanaged=" in options:
961 continue
962
963 # It's a good line, parse it.
964 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700965 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800966 options = options.split(",")
967 for i in options:
968 if i.startswith("length="):
969 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700970 elif i == "slotselect":
971 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800972 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800973 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700974 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800975
Tao Baod1de6f32017-03-01 16:38:48 -0800976 mount_flags = pieces[3]
977 # Honor the SELinux context if present.
978 context = None
979 for i in mount_flags.split(","):
980 if i.startswith("context="):
981 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800982
Tao Baod1de6f32017-03-01 16:38:48 -0800983 mount_point = pieces[1]
984 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700985 device=pieces[0], length=length, context=context,
986 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800987
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700988 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700989 # system. Other areas assume system is always at "/system" so point /system
990 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700991 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800992 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700993 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700994 return d
995
996
Tao Bao765668f2019-10-04 22:03:00 -0700997def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
998 """Finds the path to recovery fstab and loads its contents."""
999 # recovery fstab is only meaningful when installing an update via recovery
1000 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001001 if info_dict.get('ab_update') == 'true' and \
1002 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001003 return None
1004
1005 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1006 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1007 # cases, since it may load the info_dict from an old build (e.g. when
1008 # generating incremental OTAs from that build).
1009 system_root_image = info_dict.get('system_root_image') == 'true'
1010 if info_dict.get('no_recovery') != 'true':
1011 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1012 if isinstance(input_file, zipfile.ZipFile):
1013 if recovery_fstab_path not in input_file.namelist():
1014 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1015 else:
1016 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1017 if not os.path.exists(path):
1018 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1019 return LoadRecoveryFSTab(
1020 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1021 system_root_image)
1022
1023 if info_dict.get('recovery_as_boot') == 'true':
1024 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1025 if isinstance(input_file, zipfile.ZipFile):
1026 if recovery_fstab_path not in input_file.namelist():
1027 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1028 else:
1029 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1030 if not os.path.exists(path):
1031 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1032 return LoadRecoveryFSTab(
1033 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1034 system_root_image)
1035
1036 return None
1037
1038
Doug Zongker37974732010-09-16 17:44:38 -07001039def DumpInfoDict(d):
1040 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001041 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001042
Dan Albert8b72aef2015-03-23 19:13:21 -07001043
Daniel Norman55417142019-11-25 16:04:36 -08001044def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001045 """Merges dynamic partition info variables.
1046
1047 Args:
1048 framework_dict: The dictionary of dynamic partition info variables from the
1049 partial framework target files.
1050 vendor_dict: The dictionary of dynamic partition info variables from the
1051 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001052
1053 Returns:
1054 The merged dynamic partition info dictionary.
1055 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001056
1057 def uniq_concat(a, b):
1058 combined = set(a.split(" "))
1059 combined.update(set(b.split(" ")))
1060 combined = [item.strip() for item in combined if item.strip()]
1061 return " ".join(sorted(combined))
1062
1063 if (framework_dict.get("use_dynamic_partitions") !=
1064 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1065 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1066
1067 merged_dict = {"use_dynamic_partitions": "true"}
1068
1069 merged_dict["dynamic_partition_list"] = uniq_concat(
1070 framework_dict.get("dynamic_partition_list", ""),
1071 vendor_dict.get("dynamic_partition_list", ""))
1072
1073 # Super block devices are defined by the vendor dict.
1074 if "super_block_devices" in vendor_dict:
1075 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1076 for block_device in merged_dict["super_block_devices"].split(" "):
1077 key = "super_%s_device_size" % block_device
1078 if key not in vendor_dict:
1079 raise ValueError("Vendor dict does not contain required key %s." % key)
1080 merged_dict[key] = vendor_dict[key]
1081
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001082 # Partition groups and group sizes are defined by the vendor dict because
1083 # these values may vary for each board that uses a shared system image.
1084 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 for partition_group in merged_dict["super_partition_groups"].split(" "):
1086 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001087 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001088 if key not in vendor_dict:
1089 raise ValueError("Vendor dict does not contain required key %s." % key)
1090 merged_dict[key] = vendor_dict[key]
1091
1092 # Set the partition group's partition list using a concatenation of the
1093 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001094 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001095 merged_dict[key] = uniq_concat(
1096 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301097
Daniel Normanb0c75912020-09-24 14:30:21 -07001098 # Various other flags should be copied from the vendor dict, if defined.
1099 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1100 "super_metadata_device", "super_partition_error_limit",
1101 "super_partition_size"):
1102 if key in vendor_dict.keys():
1103 merged_dict[key] = vendor_dict[key]
1104
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001105 return merged_dict
1106
1107
Daniel Norman21c34f72020-11-11 17:25:50 -08001108def PartitionMapFromTargetFiles(target_files_dir):
1109 """Builds a map from partition -> path within an extracted target files directory."""
1110 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1111 possible_subdirs = {
1112 "system": ["SYSTEM"],
1113 "vendor": ["VENDOR", "SYSTEM/vendor"],
1114 "product": ["PRODUCT", "SYSTEM/product"],
1115 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1116 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1117 "vendor_dlkm": [
1118 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1119 ],
1120 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1121 }
1122 partition_map = {}
1123 for partition, subdirs in possible_subdirs.items():
1124 for subdir in subdirs:
1125 if os.path.exists(os.path.join(target_files_dir, subdir)):
1126 partition_map[partition] = subdir
1127 break
1128 return partition_map
1129
1130
Daniel Normand3351562020-10-29 12:33:11 -07001131def SharedUidPartitionViolations(uid_dict, partition_groups):
1132 """Checks for APK sharedUserIds that cross partition group boundaries.
1133
1134 This uses a single or merged build's shareduid_violation_modules.json
1135 output file, as generated by find_shareduid_violation.py or
1136 core/tasks/find-shareduid-violation.mk.
1137
1138 An error is defined as a sharedUserId that is found in a set of partitions
1139 that span more than one partition group.
1140
1141 Args:
1142 uid_dict: A dictionary created by using the standard json module to read a
1143 complete shareduid_violation_modules.json file.
1144 partition_groups: A list of groups, where each group is a list of
1145 partitions.
1146
1147 Returns:
1148 A list of error messages.
1149 """
1150 errors = []
1151 for uid, partitions in uid_dict.items():
1152 found_in_groups = [
1153 group for group in partition_groups
1154 if set(partitions.keys()) & set(group)
1155 ]
1156 if len(found_in_groups) > 1:
1157 errors.append(
1158 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1159 % (uid, ",".join(sorted(partitions.keys()))))
1160 return errors
1161
1162
Daniel Norman21c34f72020-11-11 17:25:50 -08001163def RunHostInitVerifier(product_out, partition_map):
1164 """Runs host_init_verifier on the init rc files within partitions.
1165
1166 host_init_verifier searches the etc/init path within each partition.
1167
1168 Args:
1169 product_out: PRODUCT_OUT directory, containing partition directories.
1170 partition_map: A map of partition name -> relative path within product_out.
1171 """
1172 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1173 cmd = ["host_init_verifier"]
1174 for partition, path in partition_map.items():
1175 if partition not in allowed_partitions:
1176 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1177 partition)
1178 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1179 # Add --property-contexts if the file exists on the partition.
1180 property_contexts = "%s_property_contexts" % (
1181 "plat" if partition == "system" else partition)
1182 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1183 property_contexts)
1184 if os.path.exists(property_contexts_path):
1185 cmd.append("--property-contexts=%s" % property_contexts_path)
1186 # Add the passwd file if the file exists on the partition.
1187 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1188 if os.path.exists(passwd_path):
1189 cmd.extend(["-p", passwd_path])
1190 return RunAndCheckOutput(cmd)
1191
1192
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001193def AppendAVBSigningArgs(cmd, partition):
1194 """Append signing arguments for avbtool."""
1195 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1196 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001197 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1198 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1199 if os.path.exists(new_key_path):
1200 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001201 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1202 if key_path and algorithm:
1203 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001204 avb_salt = OPTIONS.info_dict.get("avb_salt")
1205 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001206 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001207 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001208
1209
Tao Bao765668f2019-10-04 22:03:00 -07001210def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001211 """Returns the VBMeta arguments for partition.
1212
1213 It sets up the VBMeta argument by including the partition descriptor from the
1214 given 'image', or by configuring the partition as a chained partition.
1215
1216 Args:
1217 partition: The name of the partition (e.g. "system").
1218 image: The path to the partition image.
1219 info_dict: A dict returned by common.LoadInfoDict(). Will use
1220 OPTIONS.info_dict if None has been given.
1221
1222 Returns:
1223 A list of VBMeta arguments.
1224 """
1225 if info_dict is None:
1226 info_dict = OPTIONS.info_dict
1227
1228 # Check if chain partition is used.
1229 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001230 if not key_path:
1231 return ["--include_descriptors_from_image", image]
1232
1233 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1234 # into vbmeta.img. The recovery image will be configured on an independent
1235 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1236 # See details at
1237 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001238 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001239 return []
1240
1241 # Otherwise chain the partition into vbmeta.
1242 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1243 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001244
1245
Tao Bao02a08592018-07-22 12:40:45 -07001246def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1247 """Constructs and returns the arg to build or verify a chained partition.
1248
1249 Args:
1250 partition: The partition name.
1251 info_dict: The info dict to look up the key info and rollback index
1252 location.
1253 key: The key to be used for building or verifying the partition. Defaults to
1254 the key listed in info_dict.
1255
1256 Returns:
1257 A string of form "partition:rollback_index_location:key" that can be used to
1258 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001259 """
1260 if key is None:
1261 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001262 if key and not os.path.exists(key) and OPTIONS.search_path:
1263 new_key_path = os.path.join(OPTIONS.search_path, key)
1264 if os.path.exists(new_key_path):
1265 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001266 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001267 rollback_index_location = info_dict[
1268 "avb_" + partition + "_rollback_index_location"]
1269 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1270
1271
Tianjie20dd8f22020-04-19 15:51:16 -07001272def ConstructAftlMakeImageCommands(output_image):
1273 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001274
1275 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001276 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001277 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1278 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1279 'No AFTL manufacturer key provided.'
1280
1281 vbmeta_image = MakeTempFile()
1282 os.rename(output_image, vbmeta_image)
1283 build_info = BuildInfo(OPTIONS.info_dict)
1284 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001285 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001286 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001287 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001288 "--vbmeta_image_path", vbmeta_image,
1289 "--output", output_image,
1290 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001291 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001292 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1293 "--algorithm", "SHA256_RSA4096",
1294 "--padding", "4096"]
1295 if OPTIONS.aftl_signer_helper:
1296 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001297 return aftl_cmd
1298
1299
1300def AddAftlInclusionProof(output_image):
1301 """Appends the aftl inclusion proof to the vbmeta image."""
1302
1303 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001304 RunAndCheckOutput(aftl_cmd)
1305
1306 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1307 output_image, '--transparency_log_pub_keys',
1308 OPTIONS.aftl_key_path]
1309 RunAndCheckOutput(verify_cmd)
1310
1311
Daniel Norman276f0622019-07-26 14:13:51 -07001312def BuildVBMeta(image_path, partitions, name, needed_partitions):
1313 """Creates a VBMeta image.
1314
1315 It generates the requested VBMeta image. The requested image could be for
1316 top-level or chained VBMeta image, which is determined based on the name.
1317
1318 Args:
1319 image_path: The output path for the new VBMeta image.
1320 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001321 values. Only valid partition names are accepted, as partitions listed
1322 in common.AVB_PARTITIONS and custom partitions listed in
1323 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001324 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1325 needed_partitions: Partitions whose descriptors should be included into the
1326 generated VBMeta image.
1327
1328 Raises:
1329 AssertionError: On invalid input args.
1330 """
1331 avbtool = OPTIONS.info_dict["avb_avbtool"]
1332 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1333 AppendAVBSigningArgs(cmd, name)
1334
Hongguang Chenf23364d2020-04-27 18:36:36 -07001335 custom_partitions = OPTIONS.info_dict.get(
1336 "avb_custom_images_partition_list", "").strip().split()
1337
Daniel Norman276f0622019-07-26 14:13:51 -07001338 for partition, path in partitions.items():
1339 if partition not in needed_partitions:
1340 continue
1341 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001342 partition in AVB_VBMETA_PARTITIONS or
1343 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001344 'Unknown partition: {}'.format(partition)
1345 assert os.path.exists(path), \
1346 'Failed to find {} for {}'.format(path, partition)
1347 cmd.extend(GetAvbPartitionArg(partition, path))
1348
1349 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1350 if args and args.strip():
1351 split_args = shlex.split(args)
1352 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001353 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001354 # as a path relative to source tree, which may not be available at the
1355 # same location when running this script (we have the input target_files
1356 # zip only). For such cases, we additionally scan other locations (e.g.
1357 # IMAGES/, RADIO/, etc) before bailing out.
1358 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001359 chained_image = split_args[index + 1]
1360 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001361 continue
1362 found = False
1363 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1364 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001365 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001366 if os.path.exists(alt_path):
1367 split_args[index + 1] = alt_path
1368 found = True
1369 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001370 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001371 cmd.extend(split_args)
1372
1373 RunAndCheckOutput(cmd)
1374
Tianjie Xueaed60c2020-03-12 00:33:28 -07001375 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001376 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001377 AddAftlInclusionProof(image_path)
1378
Daniel Norman276f0622019-07-26 14:13:51 -07001379
J. Avila98cd4cc2020-06-10 20:09:10 +00001380def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001381 ramdisk_img = tempfile.NamedTemporaryFile()
1382
1383 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1384 cmd = ["mkbootfs", "-f", fs_config_file,
1385 os.path.join(sourcedir, "RAMDISK")]
1386 else:
1387 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1388 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001389 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001390 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001391 stdout=ramdisk_img.file.fileno())
1392 else:
1393 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001394
1395 p2.wait()
1396 p1.wait()
1397 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001398 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001399
1400 return ramdisk_img
1401
1402
Steve Muckle9793cf62020-04-08 18:27:00 -07001403def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001404 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001405 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001406
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001407 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001408 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1409 we are building a two-step special image (i.e. building a recovery image to
1410 be loaded into /boot in two-step OTAs).
1411
1412 Return the image data, or None if sourcedir does not appear to contains files
1413 for building the requested image.
1414 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001415
Yifan Hong63c5ca12020-10-08 11:54:02 -07001416 if info_dict is None:
1417 info_dict = OPTIONS.info_dict
1418
Steve Muckle9793cf62020-04-08 18:27:00 -07001419 # "boot" or "recovery", without extension.
1420 partition_name = os.path.basename(sourcedir).lower()
1421
Yifan Hong63c5ca12020-10-08 11:54:02 -07001422 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001423 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001424 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1425 logger.info("Excluded kernel binary from recovery image.")
1426 else:
1427 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001428 else:
1429 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001430 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001431 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001432 return None
1433
1434 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001435 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001436
Doug Zongkereef39442009-04-02 12:14:19 -07001437 img = tempfile.NamedTemporaryFile()
1438
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001439 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001440 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1441 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001442
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001443 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1444 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1445
Yifan Hong63c5ca12020-10-08 11:54:02 -07001446 cmd = [mkbootimg]
1447 if kernel:
1448 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001449
Benoit Fradina45a8682014-07-14 21:00:43 +02001450 fn = os.path.join(sourcedir, "second")
1451 if os.access(fn, os.F_OK):
1452 cmd.append("--second")
1453 cmd.append(fn)
1454
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001455 fn = os.path.join(sourcedir, "dtb")
1456 if os.access(fn, os.F_OK):
1457 cmd.append("--dtb")
1458 cmd.append(fn)
1459
Doug Zongker171f1cd2009-06-15 22:36:37 -07001460 fn = os.path.join(sourcedir, "cmdline")
1461 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001462 cmd.append("--cmdline")
1463 cmd.append(open(fn).read().rstrip("\n"))
1464
1465 fn = os.path.join(sourcedir, "base")
1466 if os.access(fn, os.F_OK):
1467 cmd.append("--base")
1468 cmd.append(open(fn).read().rstrip("\n"))
1469
Ying Wang4de6b5b2010-08-25 14:29:34 -07001470 fn = os.path.join(sourcedir, "pagesize")
1471 if os.access(fn, os.F_OK):
1472 cmd.append("--pagesize")
1473 cmd.append(open(fn).read().rstrip("\n"))
1474
Steve Mucklef84668e2020-03-16 19:13:46 -07001475 if partition_name == "recovery":
1476 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301477 if not args:
1478 # Fall back to "mkbootimg_args" for recovery image
1479 # in case "recovery_mkbootimg_args" is not set.
1480 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001481 else:
1482 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001483 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001484 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001485
Tao Bao76def242017-11-21 09:25:31 -08001486 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001487 if args and args.strip():
1488 cmd.extend(shlex.split(args))
1489
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001490 if has_ramdisk:
1491 cmd.extend(["--ramdisk", ramdisk_img.name])
1492
Tao Baod95e9fd2015-03-29 23:07:41 -07001493 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001494 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001495 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001496 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001497 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001498 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001499
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001500 if partition_name == "recovery":
1501 if info_dict.get("include_recovery_dtbo") == "true":
1502 fn = os.path.join(sourcedir, "recovery_dtbo")
1503 cmd.extend(["--recovery_dtbo", fn])
1504 if info_dict.get("include_recovery_acpio") == "true":
1505 fn = os.path.join(sourcedir, "recovery_acpio")
1506 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001507
Tao Bao986ee862018-10-04 15:46:16 -07001508 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001509
Tao Bao76def242017-11-21 09:25:31 -08001510 if (info_dict.get("boot_signer") == "true" and
1511 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001512 # Hard-code the path as "/boot" for two-step special recovery image (which
1513 # will be loaded into /boot during the two-step OTA).
1514 if two_step_image:
1515 path = "/boot"
1516 else:
Tao Baobf70c312017-07-11 17:27:55 -07001517 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001518 cmd = [OPTIONS.boot_signer_path]
1519 cmd.extend(OPTIONS.boot_signer_args)
1520 cmd.extend([path, img.name,
1521 info_dict["verity_key"] + ".pk8",
1522 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001523 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001524
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001526 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001527 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001528 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001529 # We have switched from the prebuilt futility binary to using the tool
1530 # (futility-host) built from the source. Override the setting in the old
1531 # TF.zip.
1532 futility = info_dict["futility"]
1533 if futility.startswith("prebuilts/"):
1534 futility = "futility-host"
1535 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001536 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001537 info_dict["vboot_key"] + ".vbprivk",
1538 info_dict["vboot_subkey"] + ".vbprivk",
1539 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001540 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001541 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001542
Tao Baof3282b42015-04-01 11:21:55 -07001543 # Clean up the temp files.
1544 img_unsigned.close()
1545 img_keyblock.close()
1546
David Zeuthen8fecb282017-12-01 16:24:01 -05001547 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001548 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001549 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001550 if partition_name == "recovery":
1551 part_size = info_dict["recovery_size"]
1552 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001553 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001554 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001555 "--partition_size", str(part_size), "--partition_name",
1556 partition_name]
1557 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001558 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001559 if args and args.strip():
1560 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001561 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001562
1563 img.seek(os.SEEK_SET, 0)
1564 data = img.read()
1565
1566 if has_ramdisk:
1567 ramdisk_img.close()
1568 img.close()
1569
1570 return data
1571
1572
Doug Zongkerd5131602012-08-02 14:46:42 -07001573def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001574 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001575 """Return a File object with the desired bootable image.
1576
1577 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1578 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1579 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001580
Doug Zongker55d93282011-01-25 17:03:34 -08001581 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1582 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001583 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001584 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001585
1586 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1587 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001588 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001589 return File.FromLocalFile(name, prebuilt_path)
1590
Tao Bao32fcdab2018-10-12 10:30:39 -07001591 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001592
1593 if info_dict is None:
1594 info_dict = OPTIONS.info_dict
1595
1596 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001597 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1598 # for recovery.
1599 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1600 prebuilt_name != "boot.img" or
1601 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001602
Doug Zongker6f1d0312014-08-22 08:07:12 -07001603 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001604 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001605 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001606 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001607 if data:
1608 return File(name, data)
1609 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001610
Doug Zongkereef39442009-04-02 12:14:19 -07001611
Steve Mucklee1b10862019-07-10 10:49:37 -07001612def _BuildVendorBootImage(sourcedir, info_dict=None):
1613 """Build a vendor boot image from the specified sourcedir.
1614
1615 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1616 turn them into a vendor boot image.
1617
1618 Return the image data, or None if sourcedir does not appear to contains files
1619 for building the requested image.
1620 """
1621
1622 if info_dict is None:
1623 info_dict = OPTIONS.info_dict
1624
1625 img = tempfile.NamedTemporaryFile()
1626
J. Avila98cd4cc2020-06-10 20:09:10 +00001627 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1628 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001629
1630 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1631 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1632
1633 cmd = [mkbootimg]
1634
1635 fn = os.path.join(sourcedir, "dtb")
1636 if os.access(fn, os.F_OK):
1637 cmd.append("--dtb")
1638 cmd.append(fn)
1639
1640 fn = os.path.join(sourcedir, "vendor_cmdline")
1641 if os.access(fn, os.F_OK):
1642 cmd.append("--vendor_cmdline")
1643 cmd.append(open(fn).read().rstrip("\n"))
1644
1645 fn = os.path.join(sourcedir, "base")
1646 if os.access(fn, os.F_OK):
1647 cmd.append("--base")
1648 cmd.append(open(fn).read().rstrip("\n"))
1649
1650 fn = os.path.join(sourcedir, "pagesize")
1651 if os.access(fn, os.F_OK):
1652 cmd.append("--pagesize")
1653 cmd.append(open(fn).read().rstrip("\n"))
1654
1655 args = info_dict.get("mkbootimg_args")
1656 if args and args.strip():
1657 cmd.extend(shlex.split(args))
1658
1659 args = info_dict.get("mkbootimg_version_args")
1660 if args and args.strip():
1661 cmd.extend(shlex.split(args))
1662
1663 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1664 cmd.extend(["--vendor_boot", img.name])
1665
1666 RunAndCheckOutput(cmd)
1667
1668 # AVB: if enabled, calculate and add hash.
1669 if info_dict.get("avb_enable") == "true":
1670 avbtool = info_dict["avb_avbtool"]
1671 part_size = info_dict["vendor_boot_size"]
1672 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001673 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001674 AppendAVBSigningArgs(cmd, "vendor_boot")
1675 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1676 if args and args.strip():
1677 cmd.extend(shlex.split(args))
1678 RunAndCheckOutput(cmd)
1679
1680 img.seek(os.SEEK_SET, 0)
1681 data = img.read()
1682
1683 ramdisk_img.close()
1684 img.close()
1685
1686 return data
1687
1688
1689def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1690 info_dict=None):
1691 """Return a File object with the desired vendor boot image.
1692
1693 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1694 the source files in 'unpack_dir'/'tree_subdir'."""
1695
1696 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1697 if os.path.exists(prebuilt_path):
1698 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1699 return File.FromLocalFile(name, prebuilt_path)
1700
1701 logger.info("building image from target_files %s...", tree_subdir)
1702
1703 if info_dict is None:
1704 info_dict = OPTIONS.info_dict
1705
Kelvin Zhang0876c412020-06-23 15:06:58 -04001706 data = _BuildVendorBootImage(
1707 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001708 if data:
1709 return File(name, data)
1710 return None
1711
1712
Narayan Kamatha07bf042017-08-14 14:49:21 +01001713def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001714 """Gunzips the given gzip compressed file to a given output file."""
1715 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001716 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001717 shutil.copyfileobj(in_file, out_file)
1718
1719
Tao Bao0ff15de2019-03-20 11:26:06 -07001720def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001721 """Unzips the archive to the given directory.
1722
1723 Args:
1724 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001725 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001726 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1727 archvie. Non-matching patterns will be filtered out. If there's no match
1728 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001729 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001730 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001731 if patterns is not None:
1732 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001733 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001734 names = input_zip.namelist()
1735 filtered = [
1736 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1737
1738 # There isn't any matching files. Don't unzip anything.
1739 if not filtered:
1740 return
1741 cmd.extend(filtered)
1742
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001743 RunAndCheckOutput(cmd)
1744
1745
Doug Zongker75f17362009-12-08 13:46:44 -08001746def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001747 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001748
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001749 Args:
1750 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1751 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1752
1753 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1754 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001755
Tao Bao1c830bf2017-12-25 10:43:47 -08001756 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001757 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001758 """
Doug Zongkereef39442009-04-02 12:14:19 -07001759
Tao Bao1c830bf2017-12-25 10:43:47 -08001760 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001761 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1762 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001763 UnzipToDir(m.group(1), tmp, pattern)
1764 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001765 filename = m.group(1)
1766 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001767 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001768
Tao Baodba59ee2018-01-09 13:21:02 -08001769 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001770
1771
Yifan Hong8a66a712019-04-04 15:37:57 -07001772def GetUserImage(which, tmpdir, input_zip,
1773 info_dict=None,
1774 allow_shared_blocks=None,
1775 hashtree_info_generator=None,
1776 reset_file_map=False):
1777 """Returns an Image object suitable for passing to BlockImageDiff.
1778
1779 This function loads the specified image from the given path. If the specified
1780 image is sparse, it also performs additional processing for OTA purpose. For
1781 example, it always adds block 0 to clobbered blocks list. It also detects
1782 files that cannot be reconstructed from the block list, for whom we should
1783 avoid applying imgdiff.
1784
1785 Args:
1786 which: The partition name.
1787 tmpdir: The directory that contains the prebuilt image and block map file.
1788 input_zip: The target-files ZIP archive.
1789 info_dict: The dict to be looked up for relevant info.
1790 allow_shared_blocks: If image is sparse, whether having shared blocks is
1791 allowed. If none, it is looked up from info_dict.
1792 hashtree_info_generator: If present and image is sparse, generates the
1793 hashtree_info for this sparse image.
1794 reset_file_map: If true and image is sparse, reset file map before returning
1795 the image.
1796 Returns:
1797 A Image object. If it is a sparse image and reset_file_map is False, the
1798 image will have file_map info loaded.
1799 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001800 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001801 info_dict = LoadInfoDict(input_zip)
1802
1803 is_sparse = info_dict.get("extfs_sparse_flag")
1804
1805 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1806 # shared blocks (i.e. some blocks will show up in multiple files' block
1807 # list). We can only allocate such shared blocks to the first "owner", and
1808 # disable imgdiff for all later occurrences.
1809 if allow_shared_blocks is None:
1810 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1811
1812 if is_sparse:
1813 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1814 hashtree_info_generator)
1815 if reset_file_map:
1816 img.ResetFileMap()
1817 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001818 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001819
1820
1821def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1822 """Returns a Image object suitable for passing to BlockImageDiff.
1823
1824 This function loads the specified non-sparse image from the given path.
1825
1826 Args:
1827 which: The partition name.
1828 tmpdir: The directory that contains the prebuilt image and block map file.
1829 Returns:
1830 A Image object.
1831 """
1832 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1833 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1834
1835 # The image and map files must have been created prior to calling
1836 # ota_from_target_files.py (since LMP).
1837 assert os.path.exists(path) and os.path.exists(mappath)
1838
Tianjie Xu41976c72019-07-03 13:57:01 -07001839 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1840
Yifan Hong8a66a712019-04-04 15:37:57 -07001841
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001842def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1843 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001844 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1845
1846 This function loads the specified sparse image from the given path, and
1847 performs additional processing for OTA purpose. For example, it always adds
1848 block 0 to clobbered blocks list. It also detects files that cannot be
1849 reconstructed from the block list, for whom we should avoid applying imgdiff.
1850
1851 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001852 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001853 tmpdir: The directory that contains the prebuilt image and block map file.
1854 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001855 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001856 hashtree_info_generator: If present, generates the hashtree_info for this
1857 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001858 Returns:
1859 A SparseImage object, with file_map info loaded.
1860 """
Tao Baoc765cca2018-01-31 17:32:40 -08001861 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1862 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1863
1864 # The image and map files must have been created prior to calling
1865 # ota_from_target_files.py (since LMP).
1866 assert os.path.exists(path) and os.path.exists(mappath)
1867
1868 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1869 # it to clobbered_blocks so that it will be written to the target
1870 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1871 clobbered_blocks = "0"
1872
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001873 image = sparse_img.SparseImage(
1874 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1875 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001876
1877 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1878 # if they contain all zeros. We can't reconstruct such a file from its block
1879 # list. Tag such entries accordingly. (Bug: 65213616)
1880 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001881 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001882 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001883 continue
1884
Tom Cherryd14b8952018-08-09 14:26:00 -07001885 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1886 # filename listed in system.map may contain an additional leading slash
1887 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1888 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001889 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001890
Tom Cherryd14b8952018-08-09 14:26:00 -07001891 # Special handling another case, where files not under /system
1892 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001893 if which == 'system' and not arcname.startswith('SYSTEM'):
1894 arcname = 'ROOT/' + arcname
1895
1896 assert arcname in input_zip.namelist(), \
1897 "Failed to find the ZIP entry for {}".format(entry)
1898
Tao Baoc765cca2018-01-31 17:32:40 -08001899 info = input_zip.getinfo(arcname)
1900 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001901
1902 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001903 # image, check the original block list to determine its completeness. Note
1904 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001905 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001906 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001907
Tao Baoc765cca2018-01-31 17:32:40 -08001908 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1909 ranges.extra['incomplete'] = True
1910
1911 return image
1912
1913
Doug Zongkereef39442009-04-02 12:14:19 -07001914def GetKeyPasswords(keylist):
1915 """Given a list of keys, prompt the user to enter passwords for
1916 those which require them. Return a {key: password} dict. password
1917 will be None if the key has no password."""
1918
Doug Zongker8ce7c252009-05-22 13:34:54 -07001919 no_passwords = []
1920 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001921 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001922 devnull = open("/dev/null", "w+b")
1923 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001924 # We don't need a password for things that aren't really keys.
1925 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001926 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001927 continue
1928
T.R. Fullhart37e10522013-03-18 10:31:26 -07001929 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001930 "-inform", "DER", "-nocrypt"],
1931 stdin=devnull.fileno(),
1932 stdout=devnull.fileno(),
1933 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001934 p.communicate()
1935 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001936 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001937 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001938 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001939 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1940 "-inform", "DER", "-passin", "pass:"],
1941 stdin=devnull.fileno(),
1942 stdout=devnull.fileno(),
1943 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001944 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001945 if p.returncode == 0:
1946 # Encrypted key with empty string as password.
1947 key_passwords[k] = ''
1948 elif stderr.startswith('Error decrypting key'):
1949 # Definitely encrypted key.
1950 # It would have said "Error reading key" if it didn't parse correctly.
1951 need_passwords.append(k)
1952 else:
1953 # Potentially, a type of key that openssl doesn't understand.
1954 # We'll let the routines in signapk.jar handle it.
1955 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001956 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001957
T.R. Fullhart37e10522013-03-18 10:31:26 -07001958 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001959 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001960 return key_passwords
1961
1962
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001963def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001964 """Gets the minSdkVersion declared in the APK.
1965
changho.shin0f125362019-07-08 10:59:00 +09001966 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001967 This can be both a decimal number (API Level) or a codename.
1968
1969 Args:
1970 apk_name: The APK filename.
1971
1972 Returns:
1973 The parsed SDK version string.
1974
1975 Raises:
1976 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001977 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001978 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001979 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001980 stderr=subprocess.PIPE)
1981 stdoutdata, stderrdata = proc.communicate()
1982 if proc.returncode != 0:
1983 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001984 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001985 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001986
Tao Baof47bf0f2018-03-21 23:28:51 -07001987 for line in stdoutdata.split("\n"):
1988 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001989 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1990 if m:
1991 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001992 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001993
1994
1995def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001996 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001997
Tao Baof47bf0f2018-03-21 23:28:51 -07001998 If minSdkVersion is set to a codename, it is translated to a number using the
1999 provided map.
2000
2001 Args:
2002 apk_name: The APK filename.
2003
2004 Returns:
2005 The parsed SDK version number.
2006
2007 Raises:
2008 ExternalError: On failing to get the min SDK version number.
2009 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002010 version = GetMinSdkVersion(apk_name)
2011 try:
2012 return int(version)
2013 except ValueError:
2014 # Not a decimal number. Codename?
2015 if version in codename_to_api_level_map:
2016 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002017 raise ExternalError(
2018 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2019 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002020
2021
2022def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002023 codename_to_api_level_map=None, whole_file=False,
2024 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002025 """Sign the input_name zip/jar/apk, producing output_name. Use the
2026 given key and password (the latter may be None if the key does not
2027 have a password.
2028
Doug Zongker951495f2009-08-14 12:44:19 -07002029 If whole_file is true, use the "-w" option to SignApk to embed a
2030 signature that covers the whole file in the archive comment of the
2031 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002032
2033 min_api_level is the API Level (int) of the oldest platform this file may end
2034 up on. If not specified for an APK, the API Level is obtained by interpreting
2035 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2036
2037 codename_to_api_level_map is needed to translate the codename which may be
2038 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002039
2040 Caller may optionally specify extra args to be passed to SignApk, which
2041 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002042 """
Tao Bao76def242017-11-21 09:25:31 -08002043 if codename_to_api_level_map is None:
2044 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002045 if extra_signapk_args is None:
2046 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002047
Alex Klyubin9667b182015-12-10 13:38:50 -08002048 java_library_path = os.path.join(
2049 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2050
Tao Baoe95540e2016-11-08 12:08:53 -08002051 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2052 ["-Djava.library.path=" + java_library_path,
2053 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002054 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002055 if whole_file:
2056 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002057
2058 min_sdk_version = min_api_level
2059 if min_sdk_version is None:
2060 if not whole_file:
2061 min_sdk_version = GetMinSdkVersionInt(
2062 input_name, codename_to_api_level_map)
2063 if min_sdk_version is not None:
2064 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2065
T.R. Fullhart37e10522013-03-18 10:31:26 -07002066 cmd.extend([key + OPTIONS.public_key_suffix,
2067 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002068 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002069
Tao Bao73dd4f42018-10-04 16:25:33 -07002070 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002071 if password is not None:
2072 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002073 stdoutdata, _ = proc.communicate(password)
2074 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002075 raise ExternalError(
2076 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002077 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002078
Doug Zongkereef39442009-04-02 12:14:19 -07002079
Doug Zongker37974732010-09-16 17:44:38 -07002080def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002081 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002082
Tao Bao9dd909e2017-11-14 11:27:32 -08002083 For non-AVB images, raise exception if the data is too big. Print a warning
2084 if the data is nearing the maximum size.
2085
2086 For AVB images, the actual image size should be identical to the limit.
2087
2088 Args:
2089 data: A string that contains all the data for the partition.
2090 target: The partition name. The ".img" suffix is optional.
2091 info_dict: The dict to be looked up for relevant info.
2092 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002093 if target.endswith(".img"):
2094 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002095 mount_point = "/" + target
2096
Ying Wangf8824af2014-06-03 14:07:27 -07002097 fs_type = None
2098 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002099 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002100 if mount_point == "/userdata":
2101 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002102 p = info_dict["fstab"][mount_point]
2103 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002104 device = p.device
2105 if "/" in device:
2106 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002107 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002108 if not fs_type or not limit:
2109 return
Doug Zongkereef39442009-04-02 12:14:19 -07002110
Andrew Boie0f9aec82012-02-14 09:32:52 -08002111 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002112 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2113 # path.
2114 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2115 if size != limit:
2116 raise ExternalError(
2117 "Mismatching image size for %s: expected %d actual %d" % (
2118 target, limit, size))
2119 else:
2120 pct = float(size) * 100.0 / limit
2121 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2122 if pct >= 99.0:
2123 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002124
2125 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002126 logger.warning("\n WARNING: %s\n", msg)
2127 else:
2128 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002129
2130
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002131def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002132 """Parses the APK certs info from a given target-files zip.
2133
2134 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2135 tuple with the following elements: (1) a dictionary that maps packages to
2136 certs (based on the "certificate" and "private_key" attributes in the file;
2137 (2) a string representing the extension of compressed APKs in the target files
2138 (e.g ".gz", ".bro").
2139
2140 Args:
2141 tf_zip: The input target_files ZipFile (already open).
2142
2143 Returns:
2144 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2145 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2146 no compressed APKs.
2147 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002148 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002149 compressed_extension = None
2150
Tao Bao0f990332017-09-08 19:02:54 -07002151 # META/apkcerts.txt contains the info for _all_ the packages known at build
2152 # time. Filter out the ones that are not installed.
2153 installed_files = set()
2154 for name in tf_zip.namelist():
2155 basename = os.path.basename(name)
2156 if basename:
2157 installed_files.add(basename)
2158
Tao Baoda30cfa2017-12-01 16:19:46 -08002159 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002160 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002161 if not line:
2162 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002163 m = re.match(
2164 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002165 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2166 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002167 line)
2168 if not m:
2169 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002170
Tao Bao818ddf52018-01-05 11:17:34 -08002171 matches = m.groupdict()
2172 cert = matches["CERT"]
2173 privkey = matches["PRIVKEY"]
2174 name = matches["NAME"]
2175 this_compressed_extension = matches["COMPRESSED"]
2176
2177 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2178 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2179 if cert in SPECIAL_CERT_STRINGS and not privkey:
2180 certmap[name] = cert
2181 elif (cert.endswith(OPTIONS.public_key_suffix) and
2182 privkey.endswith(OPTIONS.private_key_suffix) and
2183 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2184 certmap[name] = cert[:-public_key_suffix_len]
2185 else:
2186 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2187
2188 if not this_compressed_extension:
2189 continue
2190
2191 # Only count the installed files.
2192 filename = name + '.' + this_compressed_extension
2193 if filename not in installed_files:
2194 continue
2195
2196 # Make sure that all the values in the compression map have the same
2197 # extension. We don't support multiple compression methods in the same
2198 # system image.
2199 if compressed_extension:
2200 if this_compressed_extension != compressed_extension:
2201 raise ValueError(
2202 "Multiple compressed extensions: {} vs {}".format(
2203 compressed_extension, this_compressed_extension))
2204 else:
2205 compressed_extension = this_compressed_extension
2206
2207 return (certmap,
2208 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002209
2210
Doug Zongkereef39442009-04-02 12:14:19 -07002211COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002212Global options
2213
2214 -p (--path) <dir>
2215 Prepend <dir>/bin to the list of places to search for binaries run by this
2216 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002217
Doug Zongker05d3dea2009-06-22 11:32:31 -07002218 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002219 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002220
Tao Bao30df8b42018-04-23 15:32:53 -07002221 -x (--extra) <key=value>
2222 Add a key/value pair to the 'extras' dict, which device-specific extension
2223 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002224
Doug Zongkereef39442009-04-02 12:14:19 -07002225 -v (--verbose)
2226 Show command lines being executed.
2227
2228 -h (--help)
2229 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002230
2231 --logfile <file>
2232 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002233"""
2234
Kelvin Zhang0876c412020-06-23 15:06:58 -04002235
Doug Zongkereef39442009-04-02 12:14:19 -07002236def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002237 print(docstring.rstrip("\n"))
2238 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002239
2240
2241def ParseOptions(argv,
2242 docstring,
2243 extra_opts="", extra_long_opts=(),
2244 extra_option_handler=None):
2245 """Parse the options in argv and return any arguments that aren't
2246 flags. docstring is the calling module's docstring, to be displayed
2247 for errors and -h. extra_opts and extra_long_opts are for flags
2248 defined by the caller, which are processed by passing them to
2249 extra_option_handler."""
2250
2251 try:
2252 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002253 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002254 ["help", "verbose", "path=", "signapk_path=",
2255 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002256 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002257 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2258 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002259 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2260 "aftl_key_path=", "aftl_manufacturer_key_path=",
2261 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002262 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002263 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002264 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002265 sys.exit(2)
2266
Doug Zongkereef39442009-04-02 12:14:19 -07002267 for o, a in opts:
2268 if o in ("-h", "--help"):
2269 Usage(docstring)
2270 sys.exit()
2271 elif o in ("-v", "--verbose"):
2272 OPTIONS.verbose = True
2273 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002274 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002275 elif o in ("--signapk_path",):
2276 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002277 elif o in ("--signapk_shared_library_path",):
2278 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002279 elif o in ("--extra_signapk_args",):
2280 OPTIONS.extra_signapk_args = shlex.split(a)
2281 elif o in ("--java_path",):
2282 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002283 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002284 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002285 elif o in ("--android_jar_path",):
2286 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002287 elif o in ("--public_key_suffix",):
2288 OPTIONS.public_key_suffix = a
2289 elif o in ("--private_key_suffix",):
2290 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002291 elif o in ("--boot_signer_path",):
2292 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002293 elif o in ("--boot_signer_args",):
2294 OPTIONS.boot_signer_args = shlex.split(a)
2295 elif o in ("--verity_signer_path",):
2296 OPTIONS.verity_signer_path = a
2297 elif o in ("--verity_signer_args",):
2298 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002299 elif o in ("--aftl_tool_path",):
2300 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002301 elif o in ("--aftl_server",):
2302 OPTIONS.aftl_server = a
2303 elif o in ("--aftl_key_path",):
2304 OPTIONS.aftl_key_path = a
2305 elif o in ("--aftl_manufacturer_key_path",):
2306 OPTIONS.aftl_manufacturer_key_path = a
2307 elif o in ("--aftl_signer_helper",):
2308 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002309 elif o in ("-s", "--device_specific"):
2310 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002311 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002312 key, value = a.split("=", 1)
2313 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002314 elif o in ("--logfile",):
2315 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002316 else:
2317 if extra_option_handler is None or not extra_option_handler(o, a):
2318 assert False, "unknown option \"%s\"" % (o,)
2319
Doug Zongker85448772014-09-09 14:59:20 -07002320 if OPTIONS.search_path:
2321 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2322 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002323
2324 return args
2325
2326
Tao Bao4c851b12016-09-19 13:54:38 -07002327def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002328 """Make a temp file and add it to the list of things to be deleted
2329 when Cleanup() is called. Return the filename."""
2330 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2331 os.close(fd)
2332 OPTIONS.tempfiles.append(fn)
2333 return fn
2334
2335
Tao Bao1c830bf2017-12-25 10:43:47 -08002336def MakeTempDir(prefix='tmp', suffix=''):
2337 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2338
2339 Returns:
2340 The absolute pathname of the new directory.
2341 """
2342 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2343 OPTIONS.tempfiles.append(dir_name)
2344 return dir_name
2345
2346
Doug Zongkereef39442009-04-02 12:14:19 -07002347def Cleanup():
2348 for i in OPTIONS.tempfiles:
2349 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002350 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002351 else:
2352 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002353 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002354
2355
2356class PasswordManager(object):
2357 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002358 self.editor = os.getenv("EDITOR")
2359 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002360
2361 def GetPasswords(self, items):
2362 """Get passwords corresponding to each string in 'items',
2363 returning a dict. (The dict may have keys in addition to the
2364 values in 'items'.)
2365
2366 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2367 user edit that file to add more needed passwords. If no editor is
2368 available, or $ANDROID_PW_FILE isn't define, prompts the user
2369 interactively in the ordinary way.
2370 """
2371
2372 current = self.ReadFile()
2373
2374 first = True
2375 while True:
2376 missing = []
2377 for i in items:
2378 if i not in current or not current[i]:
2379 missing.append(i)
2380 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002381 if not missing:
2382 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002383
2384 for i in missing:
2385 current[i] = ""
2386
2387 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002388 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002389 if sys.version_info[0] >= 3:
2390 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002391 answer = raw_input("try to edit again? [y]> ").strip()
2392 if answer and answer[0] not in 'yY':
2393 raise RuntimeError("key passwords unavailable")
2394 first = False
2395
2396 current = self.UpdateAndReadFile(current)
2397
Kelvin Zhang0876c412020-06-23 15:06:58 -04002398 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002399 """Prompt the user to enter a value (password) for each key in
2400 'current' whose value is fales. Returns a new dict with all the
2401 values.
2402 """
2403 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002404 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002405 if v:
2406 result[k] = v
2407 else:
2408 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002409 result[k] = getpass.getpass(
2410 "Enter password for %s key> " % k).strip()
2411 if result[k]:
2412 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002413 return result
2414
2415 def UpdateAndReadFile(self, current):
2416 if not self.editor or not self.pwfile:
2417 return self.PromptResult(current)
2418
2419 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002420 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002421 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2422 f.write("# (Additional spaces are harmless.)\n\n")
2423
2424 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002425 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002426 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002427 f.write("[[[ %s ]]] %s\n" % (v, k))
2428 if not v and first_line is None:
2429 # position cursor on first line with no password.
2430 first_line = i + 4
2431 f.close()
2432
Tao Bao986ee862018-10-04 15:46:16 -07002433 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002434
2435 return self.ReadFile()
2436
2437 def ReadFile(self):
2438 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002439 if self.pwfile is None:
2440 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002441 try:
2442 f = open(self.pwfile, "r")
2443 for line in f:
2444 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002445 if not line or line[0] == '#':
2446 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002447 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2448 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002449 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002450 else:
2451 result[m.group(2)] = m.group(1)
2452 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002453 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002454 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002455 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002456 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002457
2458
Dan Albert8e0178d2015-01-27 15:53:15 -08002459def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2460 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002461
2462 # http://b/18015246
2463 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2464 # for files larger than 2GiB. We can work around this by adjusting their
2465 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2466 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2467 # it isn't clear to me exactly what circumstances cause this).
2468 # `zipfile.write()` must be used directly to work around this.
2469 #
2470 # This mess can be avoided if we port to python3.
2471 saved_zip64_limit = zipfile.ZIP64_LIMIT
2472 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2473
2474 if compress_type is None:
2475 compress_type = zip_file.compression
2476 if arcname is None:
2477 arcname = filename
2478
2479 saved_stat = os.stat(filename)
2480
2481 try:
2482 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2483 # file to be zipped and reset it when we're done.
2484 os.chmod(filename, perms)
2485
2486 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002487 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2488 # intentional. zip stores datetimes in local time without a time zone
2489 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2490 # in the zip archive.
2491 local_epoch = datetime.datetime.fromtimestamp(0)
2492 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002493 os.utime(filename, (timestamp, timestamp))
2494
2495 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2496 finally:
2497 os.chmod(filename, saved_stat.st_mode)
2498 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2499 zipfile.ZIP64_LIMIT = saved_zip64_limit
2500
2501
Tao Bao58c1b962015-05-20 09:32:18 -07002502def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002503 compress_type=None):
2504 """Wrap zipfile.writestr() function to work around the zip64 limit.
2505
2506 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2507 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2508 when calling crc32(bytes).
2509
2510 But it still works fine to write a shorter string into a large zip file.
2511 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2512 when we know the string won't be too long.
2513 """
2514
2515 saved_zip64_limit = zipfile.ZIP64_LIMIT
2516 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2517
2518 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2519 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002520 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002521 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002522 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002523 else:
Tao Baof3282b42015-04-01 11:21:55 -07002524 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002525 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2526 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2527 # such a case (since
2528 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2529 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2530 # permission bits. We follow the logic in Python 3 to get consistent
2531 # behavior between using the two versions.
2532 if not zinfo.external_attr:
2533 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002534
2535 # If compress_type is given, it overrides the value in zinfo.
2536 if compress_type is not None:
2537 zinfo.compress_type = compress_type
2538
Tao Bao58c1b962015-05-20 09:32:18 -07002539 # If perms is given, it has a priority.
2540 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002541 # If perms doesn't set the file type, mark it as a regular file.
2542 if perms & 0o770000 == 0:
2543 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002544 zinfo.external_attr = perms << 16
2545
Tao Baof3282b42015-04-01 11:21:55 -07002546 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002547 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2548
Dan Albert8b72aef2015-03-23 19:13:21 -07002549 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002550 zipfile.ZIP64_LIMIT = saved_zip64_limit
2551
2552
Tao Bao89d7ab22017-12-14 17:05:33 -08002553def ZipDelete(zip_filename, entries):
2554 """Deletes entries from a ZIP file.
2555
2556 Since deleting entries from a ZIP file is not supported, it shells out to
2557 'zip -d'.
2558
2559 Args:
2560 zip_filename: The name of the ZIP file.
2561 entries: The name of the entry, or the list of names to be deleted.
2562
2563 Raises:
2564 AssertionError: In case of non-zero return from 'zip'.
2565 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002566 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002567 entries = [entries]
2568 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002569 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002570
2571
Tao Baof3282b42015-04-01 11:21:55 -07002572def ZipClose(zip_file):
2573 # http://b/18015246
2574 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2575 # central directory.
2576 saved_zip64_limit = zipfile.ZIP64_LIMIT
2577 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2578
2579 zip_file.close()
2580
2581 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002582
2583
2584class DeviceSpecificParams(object):
2585 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002586
Doug Zongker05d3dea2009-06-22 11:32:31 -07002587 def __init__(self, **kwargs):
2588 """Keyword arguments to the constructor become attributes of this
2589 object, which is passed to all functions in the device-specific
2590 module."""
Tao Bao38884282019-07-10 22:20:56 -07002591 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002592 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002593 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002594
2595 if self.module is None:
2596 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002597 if not path:
2598 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002599 try:
2600 if os.path.isdir(path):
2601 info = imp.find_module("releasetools", [path])
2602 else:
2603 d, f = os.path.split(path)
2604 b, x = os.path.splitext(f)
2605 if x == ".py":
2606 f = b
2607 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002608 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002609 self.module = imp.load_module("device_specific", *info)
2610 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002611 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002612
2613 def _DoCall(self, function_name, *args, **kwargs):
2614 """Call the named function in the device-specific module, passing
2615 the given args and kwargs. The first argument to the call will be
2616 the DeviceSpecific object itself. If there is no module, or the
2617 module does not define the function, return the value of the
2618 'default' kwarg (which itself defaults to None)."""
2619 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002620 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002621 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2622
2623 def FullOTA_Assertions(self):
2624 """Called after emitting the block of assertions at the top of a
2625 full OTA package. Implementations can add whatever additional
2626 assertions they like."""
2627 return self._DoCall("FullOTA_Assertions")
2628
Doug Zongkere5ff5902012-01-17 10:55:37 -08002629 def FullOTA_InstallBegin(self):
2630 """Called at the start of full OTA installation."""
2631 return self._DoCall("FullOTA_InstallBegin")
2632
Yifan Hong10c530d2018-12-27 17:34:18 -08002633 def FullOTA_GetBlockDifferences(self):
2634 """Called during full OTA installation and verification.
2635 Implementation should return a list of BlockDifference objects describing
2636 the update on each additional partitions.
2637 """
2638 return self._DoCall("FullOTA_GetBlockDifferences")
2639
Doug Zongker05d3dea2009-06-22 11:32:31 -07002640 def FullOTA_InstallEnd(self):
2641 """Called at the end of full OTA installation; typically this is
2642 used to install the image for the device's baseband processor."""
2643 return self._DoCall("FullOTA_InstallEnd")
2644
2645 def IncrementalOTA_Assertions(self):
2646 """Called after emitting the block of assertions at the top of an
2647 incremental OTA package. Implementations can add whatever
2648 additional assertions they like."""
2649 return self._DoCall("IncrementalOTA_Assertions")
2650
Doug Zongkere5ff5902012-01-17 10:55:37 -08002651 def IncrementalOTA_VerifyBegin(self):
2652 """Called at the start of the verification phase of incremental
2653 OTA installation; additional checks can be placed here to abort
2654 the script before any changes are made."""
2655 return self._DoCall("IncrementalOTA_VerifyBegin")
2656
Doug Zongker05d3dea2009-06-22 11:32:31 -07002657 def IncrementalOTA_VerifyEnd(self):
2658 """Called at the end of the verification phase of incremental OTA
2659 installation; additional checks can be placed here to abort the
2660 script before any changes are made."""
2661 return self._DoCall("IncrementalOTA_VerifyEnd")
2662
Doug Zongkere5ff5902012-01-17 10:55:37 -08002663 def IncrementalOTA_InstallBegin(self):
2664 """Called at the start of incremental OTA installation (after
2665 verification is complete)."""
2666 return self._DoCall("IncrementalOTA_InstallBegin")
2667
Yifan Hong10c530d2018-12-27 17:34:18 -08002668 def IncrementalOTA_GetBlockDifferences(self):
2669 """Called during incremental OTA installation and verification.
2670 Implementation should return a list of BlockDifference objects describing
2671 the update on each additional partitions.
2672 """
2673 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2674
Doug Zongker05d3dea2009-06-22 11:32:31 -07002675 def IncrementalOTA_InstallEnd(self):
2676 """Called at the end of incremental OTA installation; typically
2677 this is used to install the image for the device's baseband
2678 processor."""
2679 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002680
Tao Bao9bc6bb22015-11-09 16:58:28 -08002681 def VerifyOTA_Assertions(self):
2682 return self._DoCall("VerifyOTA_Assertions")
2683
Tao Bao76def242017-11-21 09:25:31 -08002684
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002685class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002686 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002687 self.name = name
2688 self.data = data
2689 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002690 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002691 self.sha1 = sha1(data).hexdigest()
2692
2693 @classmethod
2694 def FromLocalFile(cls, name, diskname):
2695 f = open(diskname, "rb")
2696 data = f.read()
2697 f.close()
2698 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002699
2700 def WriteToTemp(self):
2701 t = tempfile.NamedTemporaryFile()
2702 t.write(self.data)
2703 t.flush()
2704 return t
2705
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002706 def WriteToDir(self, d):
2707 with open(os.path.join(d, self.name), "wb") as fp:
2708 fp.write(self.data)
2709
Geremy Condra36bd3652014-02-06 19:45:10 -08002710 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002711 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002712
Tao Bao76def242017-11-21 09:25:31 -08002713
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002714DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002715 ".gz": "imgdiff",
2716 ".zip": ["imgdiff", "-z"],
2717 ".jar": ["imgdiff", "-z"],
2718 ".apk": ["imgdiff", "-z"],
2719 ".img": "imgdiff",
2720}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002721
Tao Bao76def242017-11-21 09:25:31 -08002722
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002723class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002724 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002725 self.tf = tf
2726 self.sf = sf
2727 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002728 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002729
2730 def ComputePatch(self):
2731 """Compute the patch (as a string of data) needed to turn sf into
2732 tf. Returns the same tuple as GetPatch()."""
2733
2734 tf = self.tf
2735 sf = self.sf
2736
Doug Zongker24cd2802012-08-14 16:36:15 -07002737 if self.diff_program:
2738 diff_program = self.diff_program
2739 else:
2740 ext = os.path.splitext(tf.name)[1]
2741 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002742
2743 ttemp = tf.WriteToTemp()
2744 stemp = sf.WriteToTemp()
2745
2746 ext = os.path.splitext(tf.name)[1]
2747
2748 try:
2749 ptemp = tempfile.NamedTemporaryFile()
2750 if isinstance(diff_program, list):
2751 cmd = copy.copy(diff_program)
2752 else:
2753 cmd = [diff_program]
2754 cmd.append(stemp.name)
2755 cmd.append(ttemp.name)
2756 cmd.append(ptemp.name)
2757 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002758 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002759
Doug Zongkerf8340082014-08-05 10:39:37 -07002760 def run():
2761 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002762 if e:
2763 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002764 th = threading.Thread(target=run)
2765 th.start()
2766 th.join(timeout=300) # 5 mins
2767 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002768 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002769 p.terminate()
2770 th.join(5)
2771 if th.is_alive():
2772 p.kill()
2773 th.join()
2774
Tianjie Xua2a9f992018-01-05 15:15:54 -08002775 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002776 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002777 self.patch = None
2778 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002779 diff = ptemp.read()
2780 finally:
2781 ptemp.close()
2782 stemp.close()
2783 ttemp.close()
2784
2785 self.patch = diff
2786 return self.tf, self.sf, self.patch
2787
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002788 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002789 """Returns a tuple of (target_file, source_file, patch_data).
2790
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002791 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002792 computing the patch failed.
2793 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002794 return self.tf, self.sf, self.patch
2795
2796
2797def ComputeDifferences(diffs):
2798 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002799 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002800
2801 # Do the largest files first, to try and reduce the long-pole effect.
2802 by_size = [(i.tf.size, i) for i in diffs]
2803 by_size.sort(reverse=True)
2804 by_size = [i[1] for i in by_size]
2805
2806 lock = threading.Lock()
2807 diff_iter = iter(by_size) # accessed under lock
2808
2809 def worker():
2810 try:
2811 lock.acquire()
2812 for d in diff_iter:
2813 lock.release()
2814 start = time.time()
2815 d.ComputePatch()
2816 dur = time.time() - start
2817 lock.acquire()
2818
2819 tf, sf, patch = d.GetPatch()
2820 if sf.name == tf.name:
2821 name = tf.name
2822 else:
2823 name = "%s (%s)" % (tf.name, sf.name)
2824 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002825 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002826 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002827 logger.info(
2828 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2829 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002830 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002831 except Exception:
2832 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002833 raise
2834
2835 # start worker threads; wait for them all to finish.
2836 threads = [threading.Thread(target=worker)
2837 for i in range(OPTIONS.worker_threads)]
2838 for th in threads:
2839 th.start()
2840 while threads:
2841 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002842
2843
Dan Albert8b72aef2015-03-23 19:13:21 -07002844class BlockDifference(object):
2845 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002846 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002847 self.tgt = tgt
2848 self.src = src
2849 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002850 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002851 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002852
Tao Baodd2a5892015-03-12 12:32:37 -07002853 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002854 version = max(
2855 int(i) for i in
2856 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002857 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002858 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002859
Tianjie Xu41976c72019-07-03 13:57:01 -07002860 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2861 version=self.version,
2862 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002863 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002864 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002865 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002866 self.touched_src_ranges = b.touched_src_ranges
2867 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002868
Yifan Hong10c530d2018-12-27 17:34:18 -08002869 # On devices with dynamic partitions, for new partitions,
2870 # src is None but OPTIONS.source_info_dict is not.
2871 if OPTIONS.source_info_dict is None:
2872 is_dynamic_build = OPTIONS.info_dict.get(
2873 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002874 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002875 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002876 is_dynamic_build = OPTIONS.source_info_dict.get(
2877 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002878 is_dynamic_source = partition in shlex.split(
2879 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002880
Yifan Hongbb2658d2019-01-25 12:30:58 -08002881 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002882 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2883
Yifan Hongbb2658d2019-01-25 12:30:58 -08002884 # For dynamic partitions builds, check partition list in both source
2885 # and target build because new partitions may be added, and existing
2886 # partitions may be removed.
2887 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2888
Yifan Hong10c530d2018-12-27 17:34:18 -08002889 if is_dynamic:
2890 self.device = 'map_partition("%s")' % partition
2891 else:
2892 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002893 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2894 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002895 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002896 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2897 OPTIONS.source_info_dict)
2898 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002899
Tao Baod8d14be2016-02-04 14:26:02 -08002900 @property
2901 def required_cache(self):
2902 return self._required_cache
2903
Tao Bao76def242017-11-21 09:25:31 -08002904 def WriteScript(self, script, output_zip, progress=None,
2905 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002906 if not self.src:
2907 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002908 script.Print("Patching %s image unconditionally..." % (self.partition,))
2909 else:
2910 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002911
Dan Albert8b72aef2015-03-23 19:13:21 -07002912 if progress:
2913 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002914 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002915
2916 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002917 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002918
Tao Bao9bc6bb22015-11-09 16:58:28 -08002919 def WriteStrictVerifyScript(self, script):
2920 """Verify all the blocks in the care_map, including clobbered blocks.
2921
2922 This differs from the WriteVerifyScript() function: a) it prints different
2923 error messages; b) it doesn't allow half-way updated images to pass the
2924 verification."""
2925
2926 partition = self.partition
2927 script.Print("Verifying %s..." % (partition,))
2928 ranges = self.tgt.care_map
2929 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002930 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002931 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2932 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002933 self.device, ranges_str,
2934 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002935 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002936 script.AppendExtra("")
2937
Tao Baod522bdc2016-04-12 15:53:16 -07002938 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002939 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002940
2941 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002942 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002943 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002944
2945 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002946 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002947 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002948 ranges = self.touched_src_ranges
2949 expected_sha1 = self.touched_src_sha1
2950 else:
2951 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2952 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002953
2954 # No blocks to be checked, skipping.
2955 if not ranges:
2956 return
2957
Tao Bao5ece99d2015-05-12 11:42:31 -07002958 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002959 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002960 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002961 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2962 '"%s.patch.dat")) then' % (
2963 self.device, ranges_str, expected_sha1,
2964 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002965 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002966 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002967
Tianjie Xufc3422a2015-12-15 11:53:59 -08002968 if self.version >= 4:
2969
2970 # Bug: 21124327
2971 # When generating incrementals for the system and vendor partitions in
2972 # version 4 or newer, explicitly check the first block (which contains
2973 # the superblock) of the partition to see if it's what we expect. If
2974 # this check fails, give an explicit log message about the partition
2975 # having been remounted R/W (the most likely explanation).
2976 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002977 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002978
2979 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002980 if partition == "system":
2981 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2982 else:
2983 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002984 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002985 'ifelse (block_image_recover({device}, "{ranges}") && '
2986 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002987 'package_extract_file("{partition}.transfer.list"), '
2988 '"{partition}.new.dat", "{partition}.patch.dat"), '
2989 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002990 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002991 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002992 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002993
Tao Baodd2a5892015-03-12 12:32:37 -07002994 # Abort the OTA update. Note that the incremental OTA cannot be applied
2995 # even if it may match the checksum of the target partition.
2996 # a) If version < 3, operations like move and erase will make changes
2997 # unconditionally and damage the partition.
2998 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002999 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003000 if partition == "system":
3001 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3002 else:
3003 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3004 script.AppendExtra((
3005 'abort("E%d: %s partition has unexpected contents");\n'
3006 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003007
Yifan Hong10c530d2018-12-27 17:34:18 -08003008 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003009 partition = self.partition
3010 script.Print('Verifying the updated %s image...' % (partition,))
3011 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3012 ranges = self.tgt.care_map
3013 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003014 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003015 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003016 self.device, ranges_str,
3017 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003018
3019 # Bug: 20881595
3020 # Verify that extended blocks are really zeroed out.
3021 if self.tgt.extended:
3022 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003023 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003024 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003025 self.device, ranges_str,
3026 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003027 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003028 if partition == "system":
3029 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3030 else:
3031 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003032 script.AppendExtra(
3033 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003034 ' abort("E%d: %s partition has unexpected non-zero contents after '
3035 'OTA update");\n'
3036 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003037 else:
3038 script.Print('Verified the updated %s image.' % (partition,))
3039
Tianjie Xu209db462016-05-24 17:34:52 -07003040 if partition == "system":
3041 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3042 else:
3043 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3044
Tao Bao5fcaaef2015-06-01 13:40:49 -07003045 script.AppendExtra(
3046 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003047 ' abort("E%d: %s partition has unexpected contents after OTA '
3048 'update");\n'
3049 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003050
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003051 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003052 ZipWrite(output_zip,
3053 '{}.transfer.list'.format(self.path),
3054 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003055
Tao Bao76def242017-11-21 09:25:31 -08003056 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3057 # its size. Quailty 9 almost triples the compression time but doesn't
3058 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003059 # zip | brotli(quality 6) | brotli(quality 9)
3060 # compressed_size: 942M | 869M (~8% reduced) | 854M
3061 # compression_time: 75s | 265s | 719s
3062 # decompression_time: 15s | 25s | 25s
3063
3064 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003065 brotli_cmd = ['brotli', '--quality=6',
3066 '--output={}.new.dat.br'.format(self.path),
3067 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003068 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003069 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003070
3071 new_data_name = '{}.new.dat.br'.format(self.partition)
3072 ZipWrite(output_zip,
3073 '{}.new.dat.br'.format(self.path),
3074 new_data_name,
3075 compress_type=zipfile.ZIP_STORED)
3076 else:
3077 new_data_name = '{}.new.dat'.format(self.partition)
3078 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3079
Dan Albert8e0178d2015-01-27 15:53:15 -08003080 ZipWrite(output_zip,
3081 '{}.patch.dat'.format(self.path),
3082 '{}.patch.dat'.format(self.partition),
3083 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003084
Tianjie Xu209db462016-05-24 17:34:52 -07003085 if self.partition == "system":
3086 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3087 else:
3088 code = ErrorCode.VENDOR_UPDATE_FAILURE
3089
Yifan Hong10c530d2018-12-27 17:34:18 -08003090 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003091 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003092 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003093 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003094 device=self.device, partition=self.partition,
3095 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003096 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003097
Kelvin Zhang0876c412020-06-23 15:06:58 -04003098 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003099 data = source.ReadRangeSet(ranges)
3100 ctx = sha1()
3101
3102 for p in data:
3103 ctx.update(p)
3104
3105 return ctx.hexdigest()
3106
Kelvin Zhang0876c412020-06-23 15:06:58 -04003107 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003108 """Return the hash value for all zero blocks."""
3109 zero_block = '\x00' * 4096
3110 ctx = sha1()
3111 for _ in range(num_blocks):
3112 ctx.update(zero_block)
3113
3114 return ctx.hexdigest()
3115
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003116
Tianjie Xu41976c72019-07-03 13:57:01 -07003117# Expose these two classes to support vendor-specific scripts
3118DataImage = images.DataImage
3119EmptyImage = images.EmptyImage
3120
Tao Bao76def242017-11-21 09:25:31 -08003121
Doug Zongker96a57e72010-09-26 14:57:41 -07003122# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003123PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003124 "ext4": "EMMC",
3125 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003126 "f2fs": "EMMC",
3127 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003128}
Doug Zongker96a57e72010-09-26 14:57:41 -07003129
Kelvin Zhang0876c412020-06-23 15:06:58 -04003130
Yifan Hongbdb32012020-05-07 12:38:53 -07003131def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3132 """
3133 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3134 backwards compatibility. It aborts if the fstab entry has slotselect option
3135 (unless check_no_slot is explicitly set to False).
3136 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003137 fstab = info["fstab"]
3138 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003139 if check_no_slot:
3140 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003141 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003142 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3143 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003144 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003145
3146
Yifan Hongbdb32012020-05-07 12:38:53 -07003147def GetTypeAndDeviceExpr(mount_point, info):
3148 """
3149 Return the filesystem of the partition, and an edify expression that evaluates
3150 to the device at runtime.
3151 """
3152 fstab = info["fstab"]
3153 if fstab:
3154 p = fstab[mount_point]
3155 device_expr = '"%s"' % fstab[mount_point].device
3156 if p.slotselect:
3157 device_expr = 'add_slot_suffix(%s)' % device_expr
3158 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003159 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003160
3161
3162def GetEntryForDevice(fstab, device):
3163 """
3164 Returns:
3165 The first entry in fstab whose device is the given value.
3166 """
3167 if not fstab:
3168 return None
3169 for mount_point in fstab:
3170 if fstab[mount_point].device == device:
3171 return fstab[mount_point]
3172 return None
3173
Kelvin Zhang0876c412020-06-23 15:06:58 -04003174
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003175def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003176 """Parses and converts a PEM-encoded certificate into DER-encoded.
3177
3178 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3179
3180 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003181 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003182 """
3183 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003184 save = False
3185 for line in data.split("\n"):
3186 if "--END CERTIFICATE--" in line:
3187 break
3188 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003189 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003190 if "--BEGIN CERTIFICATE--" in line:
3191 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003192 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003193 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003194
Tao Bao04e1f012018-02-04 12:13:35 -08003195
3196def ExtractPublicKey(cert):
3197 """Extracts the public key (PEM-encoded) from the given certificate file.
3198
3199 Args:
3200 cert: The certificate filename.
3201
3202 Returns:
3203 The public key string.
3204
3205 Raises:
3206 AssertionError: On non-zero return from 'openssl'.
3207 """
3208 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3209 # While openssl 1.1 writes the key into the given filename followed by '-out',
3210 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3211 # stdout instead.
3212 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3213 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3214 pubkey, stderrdata = proc.communicate()
3215 assert proc.returncode == 0, \
3216 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3217 return pubkey
3218
3219
Tao Bao1ac886e2019-06-26 11:58:22 -07003220def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003221 """Extracts the AVB public key from the given public or private key.
3222
3223 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003224 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003225 key: The input key file, which should be PEM-encoded public or private key.
3226
3227 Returns:
3228 The path to the extracted AVB public key file.
3229 """
3230 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3231 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003232 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003233 return output
3234
3235
Doug Zongker412c02f2014-02-13 10:58:24 -08003236def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3237 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003238 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003239
Tao Bao6d5d6232018-03-09 17:04:42 -08003240 Most of the space in the boot and recovery images is just the kernel, which is
3241 identical for the two, so the resulting patch should be efficient. Add it to
3242 the output zip, along with a shell script that is run from init.rc on first
3243 boot to actually do the patching and install the new recovery image.
3244
3245 Args:
3246 input_dir: The top-level input directory of the target-files.zip.
3247 output_sink: The callback function that writes the result.
3248 recovery_img: File object for the recovery image.
3249 boot_img: File objects for the boot image.
3250 info_dict: A dict returned by common.LoadInfoDict() on the input
3251 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003252 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003253 if info_dict is None:
3254 info_dict = OPTIONS.info_dict
3255
Tao Bao6d5d6232018-03-09 17:04:42 -08003256 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003257 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3258
3259 if board_uses_vendorimage:
3260 # In this case, the output sink is rooted at VENDOR
3261 recovery_img_path = "etc/recovery.img"
3262 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3263 sh_dir = "bin"
3264 else:
3265 # In this case the output sink is rooted at SYSTEM
3266 recovery_img_path = "vendor/etc/recovery.img"
3267 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3268 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003269
Tao Baof2cffbd2015-07-22 12:33:18 -07003270 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003271 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003272
3273 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003274 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003275 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003276 # With system-root-image, boot and recovery images will have mismatching
3277 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3278 # to handle such a case.
3279 if system_root_image:
3280 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003281 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003282 assert not os.path.exists(path)
3283 else:
3284 diff_program = ["imgdiff"]
3285 if os.path.exists(path):
3286 diff_program.append("-b")
3287 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003288 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003289 else:
3290 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003291
3292 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3293 _, _, patch = d.ComputePatch()
3294 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003295
Dan Albertebb19aa2015-03-27 19:11:53 -07003296 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003297 # The following GetTypeAndDevice()s need to use the path in the target
3298 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003299 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3300 check_no_slot=False)
3301 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3302 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003303 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003304 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003305
Tao Baof2cffbd2015-07-22 12:33:18 -07003306 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003307
3308 # Note that we use /vendor to refer to the recovery resources. This will
3309 # work for a separate vendor partition mounted at /vendor or a
3310 # /system/vendor subdirectory on the system partition, for which init will
3311 # create a symlink from /vendor to /system/vendor.
3312
3313 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003314if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3315 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003316 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003317 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3318 log -t recovery "Installing new recovery image: succeeded" || \\
3319 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003320else
3321 log -t recovery "Recovery image already installed"
3322fi
3323""" % {'type': recovery_type,
3324 'device': recovery_device,
3325 'sha1': recovery_img.sha1,
3326 'size': recovery_img.size}
3327 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003328 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003329if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3330 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003331 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003332 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3333 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3334 log -t recovery "Installing new recovery image: succeeded" || \\
3335 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003336else
3337 log -t recovery "Recovery image already installed"
3338fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003339""" % {'boot_size': boot_img.size,
3340 'boot_sha1': boot_img.sha1,
3341 'recovery_size': recovery_img.size,
3342 'recovery_sha1': recovery_img.sha1,
3343 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003344 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003345 'recovery_type': recovery_type,
3346 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003347 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003348
Bill Peckhame868aec2019-09-17 17:06:47 -07003349 # The install script location moved from /system/etc to /system/bin in the L
3350 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3351 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003352
Tao Bao32fcdab2018-10-12 10:30:39 -07003353 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003354
Tao Baoda30cfa2017-12-01 16:19:46 -08003355 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003356
3357
3358class DynamicPartitionUpdate(object):
3359 def __init__(self, src_group=None, tgt_group=None, progress=None,
3360 block_difference=None):
3361 self.src_group = src_group
3362 self.tgt_group = tgt_group
3363 self.progress = progress
3364 self.block_difference = block_difference
3365
3366 @property
3367 def src_size(self):
3368 if not self.block_difference:
3369 return 0
3370 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3371
3372 @property
3373 def tgt_size(self):
3374 if not self.block_difference:
3375 return 0
3376 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3377
3378 @staticmethod
3379 def _GetSparseImageSize(img):
3380 if not img:
3381 return 0
3382 return img.blocksize * img.total_blocks
3383
3384
3385class DynamicGroupUpdate(object):
3386 def __init__(self, src_size=None, tgt_size=None):
3387 # None: group does not exist. 0: no size limits.
3388 self.src_size = src_size
3389 self.tgt_size = tgt_size
3390
3391
3392class DynamicPartitionsDifference(object):
3393 def __init__(self, info_dict, block_diffs, progress_dict=None,
3394 source_info_dict=None):
3395 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003396 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003397
3398 self._remove_all_before_apply = False
3399 if source_info_dict is None:
3400 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003401 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003402
Tao Baof1113e92019-06-18 12:10:14 -07003403 block_diff_dict = collections.OrderedDict(
3404 [(e.partition, e) for e in block_diffs])
3405
Yifan Hong10c530d2018-12-27 17:34:18 -08003406 assert len(block_diff_dict) == len(block_diffs), \
3407 "Duplicated BlockDifference object for {}".format(
3408 [partition for partition, count in
3409 collections.Counter(e.partition for e in block_diffs).items()
3410 if count > 1])
3411
Yifan Hong79997e52019-01-23 16:56:19 -08003412 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003413
3414 for p, block_diff in block_diff_dict.items():
3415 self._partition_updates[p] = DynamicPartitionUpdate()
3416 self._partition_updates[p].block_difference = block_diff
3417
3418 for p, progress in progress_dict.items():
3419 if p in self._partition_updates:
3420 self._partition_updates[p].progress = progress
3421
3422 tgt_groups = shlex.split(info_dict.get(
3423 "super_partition_groups", "").strip())
3424 src_groups = shlex.split(source_info_dict.get(
3425 "super_partition_groups", "").strip())
3426
3427 for g in tgt_groups:
3428 for p in shlex.split(info_dict.get(
3429 "super_%s_partition_list" % g, "").strip()):
3430 assert p in self._partition_updates, \
3431 "{} is in target super_{}_partition_list but no BlockDifference " \
3432 "object is provided.".format(p, g)
3433 self._partition_updates[p].tgt_group = g
3434
3435 for g in src_groups:
3436 for p in shlex.split(source_info_dict.get(
3437 "super_%s_partition_list" % g, "").strip()):
3438 assert p in self._partition_updates, \
3439 "{} is in source super_{}_partition_list but no BlockDifference " \
3440 "object is provided.".format(p, g)
3441 self._partition_updates[p].src_group = g
3442
Yifan Hong45433e42019-01-18 13:55:25 -08003443 target_dynamic_partitions = set(shlex.split(info_dict.get(
3444 "dynamic_partition_list", "").strip()))
3445 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3446 if u.tgt_size)
3447 assert block_diffs_with_target == target_dynamic_partitions, \
3448 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3449 list(target_dynamic_partitions), list(block_diffs_with_target))
3450
3451 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3452 "dynamic_partition_list", "").strip()))
3453 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3454 if u.src_size)
3455 assert block_diffs_with_source == source_dynamic_partitions, \
3456 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3457 list(source_dynamic_partitions), list(block_diffs_with_source))
3458
Yifan Hong10c530d2018-12-27 17:34:18 -08003459 if self._partition_updates:
3460 logger.info("Updating dynamic partitions %s",
3461 self._partition_updates.keys())
3462
Yifan Hong79997e52019-01-23 16:56:19 -08003463 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003464
3465 for g in tgt_groups:
3466 self._group_updates[g] = DynamicGroupUpdate()
3467 self._group_updates[g].tgt_size = int(info_dict.get(
3468 "super_%s_group_size" % g, "0").strip())
3469
3470 for g in src_groups:
3471 if g not in self._group_updates:
3472 self._group_updates[g] = DynamicGroupUpdate()
3473 self._group_updates[g].src_size = int(source_info_dict.get(
3474 "super_%s_group_size" % g, "0").strip())
3475
3476 self._Compute()
3477
3478 def WriteScript(self, script, output_zip, write_verify_script=False):
3479 script.Comment('--- Start patching dynamic partitions ---')
3480 for p, u in self._partition_updates.items():
3481 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3482 script.Comment('Patch partition %s' % p)
3483 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3484 write_verify_script=False)
3485
3486 op_list_path = MakeTempFile()
3487 with open(op_list_path, 'w') as f:
3488 for line in self._op_list:
3489 f.write('{}\n'.format(line))
3490
3491 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3492
3493 script.Comment('Update dynamic partition metadata')
3494 script.AppendExtra('assert(update_dynamic_partitions('
3495 'package_extract_file("dynamic_partitions_op_list")));')
3496
3497 if write_verify_script:
3498 for p, u in self._partition_updates.items():
3499 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3500 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003501 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003502
3503 for p, u in self._partition_updates.items():
3504 if u.tgt_size and u.src_size <= u.tgt_size:
3505 script.Comment('Patch partition %s' % p)
3506 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3507 write_verify_script=write_verify_script)
3508 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003509 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003510
3511 script.Comment('--- End patching dynamic partitions ---')
3512
3513 def _Compute(self):
3514 self._op_list = list()
3515
3516 def append(line):
3517 self._op_list.append(line)
3518
3519 def comment(line):
3520 self._op_list.append("# %s" % line)
3521
3522 if self._remove_all_before_apply:
3523 comment('Remove all existing dynamic partitions and groups before '
3524 'applying full OTA')
3525 append('remove_all_groups')
3526
3527 for p, u in self._partition_updates.items():
3528 if u.src_group and not u.tgt_group:
3529 append('remove %s' % p)
3530
3531 for p, u in self._partition_updates.items():
3532 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3533 comment('Move partition %s from %s to default' % (p, u.src_group))
3534 append('move %s default' % p)
3535
3536 for p, u in self._partition_updates.items():
3537 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3538 comment('Shrink partition %s from %d to %d' %
3539 (p, u.src_size, u.tgt_size))
3540 append('resize %s %s' % (p, u.tgt_size))
3541
3542 for g, u in self._group_updates.items():
3543 if u.src_size is not None and u.tgt_size is None:
3544 append('remove_group %s' % g)
3545 if (u.src_size is not None and u.tgt_size is not None and
3546 u.src_size > u.tgt_size):
3547 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3548 append('resize_group %s %d' % (g, u.tgt_size))
3549
3550 for g, u in self._group_updates.items():
3551 if u.src_size is None and u.tgt_size is not None:
3552 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3553 append('add_group %s %d' % (g, u.tgt_size))
3554 if (u.src_size is not None and u.tgt_size is not None and
3555 u.src_size < u.tgt_size):
3556 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3557 append('resize_group %s %d' % (g, u.tgt_size))
3558
3559 for p, u in self._partition_updates.items():
3560 if u.tgt_group and not u.src_group:
3561 comment('Add partition %s to group %s' % (p, u.tgt_group))
3562 append('add %s %s' % (p, u.tgt_group))
3563
3564 for p, u in self._partition_updates.items():
3565 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003566 comment('Grow partition %s from %d to %d' %
3567 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003568 append('resize %s %d' % (p, u.tgt_size))
3569
3570 for p, u in self._partition_updates.items():
3571 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3572 comment('Move partition %s from default to %s' %
3573 (p, u.tgt_group))
3574 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003575
3576
3577def GetBootImageTimestamp(boot_img):
3578 """
3579 Get timestamp from ramdisk within the boot image
3580
3581 Args:
3582 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3583
3584 Return:
3585 An integer that corresponds to the timestamp of the boot image, or None
3586 if file has unknown format. Raise exception if an unexpected error has
3587 occurred.
3588 """
3589
3590 tmp_dir = MakeTempDir('boot_', suffix='.img')
3591 try:
3592 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
3593 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3594 if not os.path.isfile(ramdisk):
3595 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3596 return None
3597 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
3598 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3599
3600 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3601 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3602 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3603 # the host environment.
3604 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
3605 cwd=extracted_ramdisk)
3606
3607 prop_file = None
3608 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3609 prop_file = os.path.join(extracted_ramdisk, search_path)
3610 if os.path.isfile(prop_file):
3611 break
3612 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
3613
3614 if not prop_file:
3615 return None
3616
3617 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3618 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3619 if timestamp:
3620 return int(timestamp)
3621 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3622 return None
3623
3624 except ExternalError as e:
3625 logger.warning('Unable to get boot image timestamp: %s', e)
3626 return None