blob: 7ac7f7e28226579e5225220cba0a20d1775620dd [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
Yifan Hong5057b952021-01-07 14:09:57 -0800131# Partitions with a build.prop file
132PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP
133
Yifan Hongc65a0542021-01-07 14:21:01 -0800134# See sysprop.mk. If file is moved, add new search paths here; don't remove
135# existing search paths.
136RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700137
Tianjie Xu209db462016-05-24 17:34:52 -0700138class ErrorCode(object):
139 """Define error_codes for failures that happen during the actual
140 update package installation.
141
142 Error codes 0-999 are reserved for failures before the package
143 installation (i.e. low battery, package verification failure).
144 Detailed code in 'bootable/recovery/error_code.h' """
145
146 SYSTEM_VERIFICATION_FAILURE = 1000
147 SYSTEM_UPDATE_FAILURE = 1001
148 SYSTEM_UNEXPECTED_CONTENTS = 1002
149 SYSTEM_NONZERO_CONTENTS = 1003
150 SYSTEM_RECOVER_FAILURE = 1004
151 VENDOR_VERIFICATION_FAILURE = 2000
152 VENDOR_UPDATE_FAILURE = 2001
153 VENDOR_UNEXPECTED_CONTENTS = 2002
154 VENDOR_NONZERO_CONTENTS = 2003
155 VENDOR_RECOVER_FAILURE = 2004
156 OEM_PROP_MISMATCH = 3000
157 FINGERPRINT_MISMATCH = 3001
158 THUMBPRINT_MISMATCH = 3002
159 OLDER_BUILD = 3003
160 DEVICE_MISMATCH = 3004
161 BAD_PATCH_FILE = 3005
162 INSUFFICIENT_CACHE_SPACE = 3006
163 TUNE_PARTITION_FAILURE = 3007
164 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800165
Tao Bao80921982018-03-21 21:02:19 -0700166
Dan Albert8b72aef2015-03-23 19:13:21 -0700167class ExternalError(RuntimeError):
168 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700169
170
Tao Bao32fcdab2018-10-12 10:30:39 -0700171def InitLogging():
172 DEFAULT_LOGGING_CONFIG = {
173 'version': 1,
174 'disable_existing_loggers': False,
175 'formatters': {
176 'standard': {
177 'format':
178 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
179 'datefmt': '%Y-%m-%d %H:%M:%S',
180 },
181 },
182 'handlers': {
183 'default': {
184 'class': 'logging.StreamHandler',
185 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700186 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 },
188 },
189 'loggers': {
190 '': {
191 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700192 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700193 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 }
195 }
196 }
197 env_config = os.getenv('LOGGING_CONFIG')
198 if env_config:
199 with open(env_config) as f:
200 config = json.load(f)
201 else:
202 config = DEFAULT_LOGGING_CONFIG
203
204 # Increase the logging level for verbose mode.
205 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700206 config = copy.deepcopy(config)
207 config['handlers']['default']['level'] = 'INFO'
208
209 if OPTIONS.logfile:
210 config = copy.deepcopy(config)
211 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400212 'class': 'logging.FileHandler',
213 'formatter': 'standard',
214 'level': 'INFO',
215 'mode': 'w',
216 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700217 }
218 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700219
220 logging.config.dictConfig(config)
221
222
Yifan Hong8e332ff2020-07-29 17:51:55 -0700223def SetHostToolLocation(tool_name, location):
224 OPTIONS.host_tools[tool_name] = location
225
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900226def FindHostToolPath(tool_name):
227 """Finds the path to the host tool.
228
229 Args:
230 tool_name: name of the tool to find
231 Returns:
232 path to the tool if found under either one of the host_tools map or under
233 the same directory as this binary is located at. If not found, tool_name
234 is returned.
235 """
236 if tool_name in OPTIONS.host_tools:
237 return OPTIONS.host_tools[tool_name]
238
239 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 tool_path = os.path.join(my_dir, tool_name)
241 if os.path.exists(tool_path):
242 return tool_path
243
244 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700245
Tao Bao39451582017-05-04 11:10:47 -0700246def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700248
Tao Bao73dd4f42018-10-04 16:25:33 -0700249 Args:
250 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700251 verbose: Whether the commands should be shown. Default to the global
252 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
254 stdin, etc. stdout and stderr will default to subprocess.PIPE and
255 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800256 universal_newlines will default to True, as most of the users in
257 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700258
259 Returns:
260 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700261 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700262 if 'stdout' not in kwargs and 'stderr' not in kwargs:
263 kwargs['stdout'] = subprocess.PIPE
264 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800265 if 'universal_newlines' not in kwargs:
266 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700267
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900268 if args:
269 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900271 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700272
Tao Bao32fcdab2018-10-12 10:30:39 -0700273 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400274 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700276 return subprocess.Popen(args, **kwargs)
277
278
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800279def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800280 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800281
282 Args:
283 args: The command represented as a list of strings.
284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
Bill Peckham889b0c62019-02-21 18:53:37 -0800290 Raises:
291 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800292 """
293 proc = Run(args, verbose=verbose, **kwargs)
294 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800295
296 if proc.returncode != 0:
297 raise ExternalError(
298 "Failed to run command '{}' (exit code {})".format(
299 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800300
301
Tao Bao986ee862018-10-04 15:46:16 -0700302def RunAndCheckOutput(args, verbose=None, **kwargs):
303 """Runs the given command and returns the output.
304
305 Args:
306 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700307 verbose: Whether the commands should be shown. Default to the global
308 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700309 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
310 stdin, etc. stdout and stderr will default to subprocess.PIPE and
311 subprocess.STDOUT respectively unless caller specifies any of them.
312
313 Returns:
314 The output string.
315
316 Raises:
317 ExternalError: On non-zero exit from the command.
318 """
Tao Bao986ee862018-10-04 15:46:16 -0700319 proc = Run(args, verbose=verbose, **kwargs)
320 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800321 if output is None:
322 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700323 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400324 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700325 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700326 if proc.returncode != 0:
327 raise ExternalError(
328 "Failed to run command '{}' (exit code {}):\n{}".format(
329 args, proc.returncode, output))
330 return output
331
332
Tao Baoc765cca2018-01-31 17:32:40 -0800333def RoundUpTo4K(value):
334 rounded_up = value + 4095
335 return rounded_up - (rounded_up % 4096)
336
337
Ying Wang7e6d4e42010-12-13 16:25:36 -0800338def CloseInheritedPipes():
339 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
340 before doing other work."""
341 if platform.system() != "Darwin":
342 return
343 for d in range(3, 1025):
344 try:
345 stat = os.fstat(d)
346 if stat is not None:
347 pipebit = stat[0] & 0x1000
348 if pipebit != 0:
349 os.close(d)
350 except OSError:
351 pass
352
353
Tao Bao1c320f82019-10-04 23:25:12 -0700354class BuildInfo(object):
355 """A class that holds the information for a given build.
356
357 This class wraps up the property querying for a given source or target build.
358 It abstracts away the logic of handling OEM-specific properties, and caches
359 the commonly used properties such as fingerprint.
360
361 There are two types of info dicts: a) build-time info dict, which is generated
362 at build time (i.e. included in a target_files zip); b) OEM info dict that is
363 specified at package generation time (via command line argument
364 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
365 having "oem_fingerprint_properties" in build-time info dict), all the queries
366 would be answered based on build-time info dict only. Otherwise if using
367 OEM-specific properties, some of them will be calculated from two info dicts.
368
369 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800370 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700371
372 Attributes:
373 info_dict: The build-time info dict.
374 is_ab: Whether it's a build that uses A/B OTA.
375 oem_dicts: A list of OEM dicts.
376 oem_props: A list of OEM properties that should be read from OEM dicts; None
377 if the build doesn't use any OEM-specific property.
378 fingerprint: The fingerprint of the build, which would be calculated based
379 on OEM properties if applicable.
380 device: The device name, which could come from OEM dicts if applicable.
381 """
382
383 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
384 "ro.product.manufacturer", "ro.product.model",
385 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700386 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
387 "product", "odm", "vendor", "system_ext", "system"]
388 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
389 "product", "product_services", "odm", "vendor", "system"]
390 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700391
Tao Bao3ed35d32019-10-07 20:48:48 -0700392 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700393 """Initializes a BuildInfo instance with the given dicts.
394
395 Note that it only wraps up the given dicts, without making copies.
396
397 Arguments:
398 info_dict: The build-time info dict.
399 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
400 that it always uses the first dict to calculate the fingerprint or the
401 device name. The rest would be used for asserting OEM properties only
402 (e.g. one package can be installed on one of these devices).
403
404 Raises:
405 ValueError: On invalid inputs.
406 """
407 self.info_dict = info_dict
408 self.oem_dicts = oem_dicts
409
410 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700411
Hongguang Chend7c160f2020-05-03 21:24:26 -0700412 # Skip _oem_props if oem_dicts is None to use BuildInfo in
413 # sign_target_files_apks
414 if self.oem_dicts:
415 self._oem_props = info_dict.get("oem_fingerprint_properties")
416 else:
417 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700418
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 def check_fingerprint(fingerprint):
420 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
421 raise ValueError(
422 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
423 "3.2.2. Build Parameters.".format(fingerprint))
424
Daniel Normand5fe8622020-01-08 17:01:11 -0800425 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800426 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800427 try:
428 fingerprint = self.CalculatePartitionFingerprint(partition)
429 check_fingerprint(fingerprint)
430 self._partition_fingerprints[partition] = fingerprint
431 except ExternalError:
432 continue
433 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800434 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 # need a fingerprint when creating the image.
436 self._partition_fingerprints[
437 "system_other"] = self._partition_fingerprints["system"]
438
Tao Bao1c320f82019-10-04 23:25:12 -0700439 # These two should be computed only after setting self._oem_props.
440 self._device = self.GetOemProperty("ro.product.device")
441 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800442 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700443
444 @property
445 def is_ab(self):
446 return self._is_ab
447
448 @property
449 def device(self):
450 return self._device
451
452 @property
453 def fingerprint(self):
454 return self._fingerprint
455
456 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700457 def oem_props(self):
458 return self._oem_props
459
460 def __getitem__(self, key):
461 return self.info_dict[key]
462
463 def __setitem__(self, key, value):
464 self.info_dict[key] = value
465
466 def get(self, key, default=None):
467 return self.info_dict.get(key, default)
468
469 def items(self):
470 return self.info_dict.items()
471
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000472 def _GetRawBuildProp(self, prop, partition):
473 prop_file = '{}.build.prop'.format(
474 partition) if partition else 'build.prop'
475 partition_props = self.info_dict.get(prop_file)
476 if not partition_props:
477 return None
478 return partition_props.GetProp(prop)
479
Daniel Normand5fe8622020-01-08 17:01:11 -0800480 def GetPartitionBuildProp(self, prop, partition):
481 """Returns the inquired build property for the provided partition."""
482 # If provided a partition for this property, only look within that
483 # partition's build.prop.
484 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
485 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
486 else:
487 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000488
489 prop_val = self._GetRawBuildProp(prop, partition)
490 if prop_val is not None:
491 return prop_val
492 raise ExternalError("couldn't find %s in %s.build.prop" %
493 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800494
Tao Bao1c320f82019-10-04 23:25:12 -0700495 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800496 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700497 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
498 return self._ResolveRoProductBuildProp(prop)
499
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000500 prop_val = self._GetRawBuildProp(prop, None)
501 if prop_val is not None:
502 return prop_val
503
504 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700505
506 def _ResolveRoProductBuildProp(self, prop):
507 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700509 if prop_val:
510 return prop_val
511
Steven Laver8e2086e2020-04-27 16:26:31 -0700512 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000513 source_order_val = self._GetRawBuildProp(
514 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700515 if source_order_val:
516 source_order = source_order_val.split(",")
517 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700518 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700519
520 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700522 raise ExternalError(
523 "Invalid ro.product.property_source_order '{}'".format(source_order))
524
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000525 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700526 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000527 "ro.product", "ro.product.{}".format(source_partition), 1)
528 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700529 if prop_val:
530 return prop_val
531
532 raise ExternalError("couldn't resolve {}".format(prop))
533
Steven Laver8e2086e2020-04-27 16:26:31 -0700534 def _GetRoProductPropsDefaultSourceOrder(self):
535 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
536 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000537 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700538 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000539 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700540 if android_version == "10":
541 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
542 # NOTE: float() conversion of android_version will have rounding error.
543 # We are checking for "9" or less, and using "< 10" is well outside of
544 # possible floating point rounding.
545 try:
546 android_version_val = float(android_version)
547 except ValueError:
548 android_version_val = 0
549 if android_version_val < 10:
550 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
551 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
552
Tianjieb37c5be2020-10-15 21:27:10 -0700553 def _GetPlatformVersion(self):
554 version_sdk = self.GetBuildProp("ro.build.version.sdk")
555 # init code switches to version_release_or_codename (see b/158483506). After
556 # API finalization, release_or_codename will be the same as release. This
557 # is the best effort to support pre-S dev stage builds.
558 if int(version_sdk) >= 30:
559 try:
560 return self.GetBuildProp("ro.build.version.release_or_codename")
561 except ExternalError:
562 logger.warning('Failed to find ro.build.version.release_or_codename')
563
564 return self.GetBuildProp("ro.build.version.release")
565
566 def _GetPartitionPlatformVersion(self, partition):
567 try:
568 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
569 partition)
570 except ExternalError:
571 return self.GetPartitionBuildProp("ro.build.version.release",
572 partition)
573
Tao Bao1c320f82019-10-04 23:25:12 -0700574 def GetOemProperty(self, key):
575 if self.oem_props is not None and key in self.oem_props:
576 return self.oem_dicts[0][key]
577 return self.GetBuildProp(key)
578
Daniel Normand5fe8622020-01-08 17:01:11 -0800579 def GetPartitionFingerprint(self, partition):
580 return self._partition_fingerprints.get(partition, None)
581
582 def CalculatePartitionFingerprint(self, partition):
583 try:
584 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
585 except ExternalError:
586 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
587 self.GetPartitionBuildProp("ro.product.brand", partition),
588 self.GetPartitionBuildProp("ro.product.name", partition),
589 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700590 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800591 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400592 self.GetPartitionBuildProp(
593 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800594 self.GetPartitionBuildProp("ro.build.type", partition),
595 self.GetPartitionBuildProp("ro.build.tags", partition))
596
Tao Bao1c320f82019-10-04 23:25:12 -0700597 def CalculateFingerprint(self):
598 if self.oem_props is None:
599 try:
600 return self.GetBuildProp("ro.build.fingerprint")
601 except ExternalError:
602 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
603 self.GetBuildProp("ro.product.brand"),
604 self.GetBuildProp("ro.product.name"),
605 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700606 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700607 self.GetBuildProp("ro.build.id"),
608 self.GetBuildProp("ro.build.version.incremental"),
609 self.GetBuildProp("ro.build.type"),
610 self.GetBuildProp("ro.build.tags"))
611 return "%s/%s/%s:%s" % (
612 self.GetOemProperty("ro.product.brand"),
613 self.GetOemProperty("ro.product.name"),
614 self.GetOemProperty("ro.product.device"),
615 self.GetBuildProp("ro.build.thumbprint"))
616
617 def WriteMountOemScript(self, script):
618 assert self.oem_props is not None
619 recovery_mount_options = self.info_dict.get("recovery_mount_options")
620 script.Mount("/oem", recovery_mount_options)
621
622 def WriteDeviceAssertions(self, script, oem_no_mount):
623 # Read the property directly if not using OEM properties.
624 if not self.oem_props:
625 script.AssertDevice(self.device)
626 return
627
628 # Otherwise assert OEM properties.
629 if not self.oem_dicts:
630 raise ExternalError(
631 "No OEM file provided to answer expected assertions")
632
633 for prop in self.oem_props.split():
634 values = []
635 for oem_dict in self.oem_dicts:
636 if prop in oem_dict:
637 values.append(oem_dict[prop])
638 if not values:
639 raise ExternalError(
640 "The OEM file is missing the property %s" % (prop,))
641 script.AssertOemProperty(prop, values, oem_no_mount)
642
643
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000644def ReadFromInputFile(input_file, fn):
645 """Reads the contents of fn from input zipfile or directory."""
646 if isinstance(input_file, zipfile.ZipFile):
647 return input_file.read(fn).decode()
648 else:
649 path = os.path.join(input_file, *fn.split("/"))
650 try:
651 with open(path) as f:
652 return f.read()
653 except IOError as e:
654 if e.errno == errno.ENOENT:
655 raise KeyError(fn)
656
657
Tao Bao410ad8b2018-08-24 12:08:38 -0700658def LoadInfoDict(input_file, repacking=False):
659 """Loads the key/value pairs from the given input target_files.
660
Tianjiea85bdf02020-07-29 11:56:19 -0700661 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700662 checks and returns the parsed key/value pairs for to the given build. It's
663 usually called early when working on input target_files files, e.g. when
664 generating OTAs, or signing builds. Note that the function may be called
665 against an old target_files file (i.e. from past dessert releases). So the
666 property parsing needs to be backward compatible.
667
668 In a `META/misc_info.txt`, a few properties are stored as links to the files
669 in the PRODUCT_OUT directory. It works fine with the build system. However,
670 they are no longer available when (re)generating images from target_files zip.
671 When `repacking` is True, redirect these properties to the actual files in the
672 unzipped directory.
673
674 Args:
675 input_file: The input target_files file, which could be an open
676 zipfile.ZipFile instance, or a str for the dir that contains the files
677 unzipped from a target_files file.
678 repacking: Whether it's trying repack an target_files file after loading the
679 info dict (default: False). If so, it will rewrite a few loaded
680 properties (e.g. selinux_fc, root_dir) to point to the actual files in
681 target_files file. When doing repacking, `input_file` must be a dir.
682
683 Returns:
684 A dict that contains the parsed key/value pairs.
685
686 Raises:
687 AssertionError: On invalid input arguments.
688 ValueError: On malformed input values.
689 """
690 if repacking:
691 assert isinstance(input_file, str), \
692 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700693
Doug Zongkerc9253822014-02-04 12:17:58 -0800694 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000695 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800696
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700697 try:
Michael Runge6e836112014-04-15 17:40:21 -0700698 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700699 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700700 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700701
Tao Bao410ad8b2018-08-24 12:08:38 -0700702 if "recovery_api_version" not in d:
703 raise ValueError("Failed to find 'recovery_api_version'")
704 if "fstab_version" not in d:
705 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800706
Tao Bao410ad8b2018-08-24 12:08:38 -0700707 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700708 # "selinux_fc" properties should point to the file_contexts files
709 # (file_contexts.bin) under META/.
710 for key in d:
711 if key.endswith("selinux_fc"):
712 fc_basename = os.path.basename(d[key])
713 fc_config = os.path.join(input_file, "META", fc_basename)
714 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700715
Daniel Norman72c626f2019-05-13 15:58:14 -0700716 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700717
Tom Cherryd14b8952018-08-09 14:26:00 -0700718 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700719 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700720 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700721 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700722
David Anderson0ec64ac2019-12-06 12:21:18 -0800723 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700724 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700725 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800726 key_name = part_name + "_base_fs_file"
727 if key_name not in d:
728 continue
729 basename = os.path.basename(d[key_name])
730 base_fs_file = os.path.join(input_file, "META", basename)
731 if os.path.exists(base_fs_file):
732 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700733 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700734 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800735 "Failed to find %s base fs file: %s", part_name, base_fs_file)
736 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700737
Doug Zongker37974732010-09-16 17:44:38 -0700738 def makeint(key):
739 if key in d:
740 d[key] = int(d[key], 0)
741
742 makeint("recovery_api_version")
743 makeint("blocksize")
744 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700745 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700746 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700747 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700748 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800749 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700750
Steve Muckle903a1ca2020-05-07 17:32:10 -0700751 boot_images = "boot.img"
752 if "boot_images" in d:
753 boot_images = d["boot_images"]
754 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400755 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700756
Tao Bao765668f2019-10-04 22:03:00 -0700757 # Load recovery fstab if applicable.
758 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800759
Tianjie Xu861f4132018-09-12 11:49:33 -0700760 # Tries to load the build props for all partitions with care_map, including
761 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800762 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800763 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000764 d[partition_prop] = PartitionBuildProps.FromInputFile(
765 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700766 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800767
Tao Bao3ed35d32019-10-07 20:48:48 -0700768 # Set up the salt (based on fingerprint) that will be used when adding AVB
769 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800770 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700771 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800772 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800773 fingerprint = build_info.GetPartitionFingerprint(partition)
774 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400775 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400776 try:
777 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
778 except KeyError:
779 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700780 return d
781
Tao Baod1de6f32017-03-01 16:38:48 -0800782
Kelvin Zhang39aea442020-08-17 11:04:25 -0400783
Daniel Norman4cc9df62019-07-18 10:11:07 -0700784def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900785 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700786 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900787
Daniel Norman4cc9df62019-07-18 10:11:07 -0700788
789def LoadDictionaryFromFile(file_path):
790 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900791 return LoadDictionaryFromLines(lines)
792
793
Michael Runge6e836112014-04-15 17:40:21 -0700794def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700795 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700796 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700797 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700798 if not line or line.startswith("#"):
799 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700800 if "=" in line:
801 name, value = line.split("=", 1)
802 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700803 return d
804
Tao Baod1de6f32017-03-01 16:38:48 -0800805
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000806class PartitionBuildProps(object):
807 """The class holds the build prop of a particular partition.
808
809 This class loads the build.prop and holds the build properties for a given
810 partition. It also partially recognizes the 'import' statement in the
811 build.prop; and calculates alternative values of some specific build
812 properties during runtime.
813
814 Attributes:
815 input_file: a zipped target-file or an unzipped target-file directory.
816 partition: name of the partition.
817 props_allow_override: a list of build properties to search for the
818 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000819 build_props: a dict of build properties for the given partition.
820 prop_overrides: a set of props that are overridden by import.
821 placeholder_values: A dict of runtime variables' values to replace the
822 placeholders in the build.prop file. We expect exactly one value for
823 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000824 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400825
Tianjie Xu9afb2212020-05-10 21:48:15 +0000826 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000827 self.input_file = input_file
828 self.partition = name
829 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000830 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000831 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000832 self.prop_overrides = set()
833 self.placeholder_values = {}
834 if placeholder_values:
835 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000836
837 @staticmethod
838 def FromDictionary(name, build_props):
839 """Constructs an instance from a build prop dictionary."""
840
841 props = PartitionBuildProps("unknown", name)
842 props.build_props = build_props.copy()
843 return props
844
845 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000846 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000847 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000848 data = ''
849 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
850 '{}/build.prop'.format(name.upper())]:
851 try:
852 data = ReadFromInputFile(input_file, prop_file)
853 break
854 except KeyError:
855 logger.warning('Failed to read %s', prop_file)
856
Tianjie Xu9afb2212020-05-10 21:48:15 +0000857 props = PartitionBuildProps(input_file, name, placeholder_values)
858 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000859 return props
860
Yifan Hong125d0b62020-09-24 17:07:03 -0700861 @staticmethod
862 def FromBuildPropFile(name, build_prop_file):
863 """Constructs an instance from a build prop file."""
864
865 props = PartitionBuildProps("unknown", name)
866 with open(build_prop_file) as f:
867 props._LoadBuildProp(f.read())
868 return props
869
Tianjie Xu9afb2212020-05-10 21:48:15 +0000870 def _LoadBuildProp(self, data):
871 for line in data.split('\n'):
872 line = line.strip()
873 if not line or line.startswith("#"):
874 continue
875 if line.startswith("import"):
876 overrides = self._ImportParser(line)
877 duplicates = self.prop_overrides.intersection(overrides.keys())
878 if duplicates:
879 raise ValueError('prop {} is overridden multiple times'.format(
880 ','.join(duplicates)))
881 self.prop_overrides = self.prop_overrides.union(overrides.keys())
882 self.build_props.update(overrides)
883 elif "=" in line:
884 name, value = line.split("=", 1)
885 if name in self.prop_overrides:
886 raise ValueError('prop {} is set again after overridden by import '
887 'statement'.format(name))
888 self.build_props[name] = value
889
890 def _ImportParser(self, line):
891 """Parses the build prop in a given import statement."""
892
893 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400894 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700896
897 if len(tokens) == 3:
898 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
899 return {}
900
Tianjie Xu9afb2212020-05-10 21:48:15 +0000901 import_path = tokens[1]
902 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
903 raise ValueError('Unrecognized import path {}'.format(line))
904
905 # We only recognize a subset of import statement that the init process
906 # supports. And we can loose the restriction based on how the dynamic
907 # fingerprint is used in practice. The placeholder format should be
908 # ${placeholder}, and its value should be provided by the caller through
909 # the placeholder_values.
910 for prop, value in self.placeholder_values.items():
911 prop_place_holder = '${{{}}}'.format(prop)
912 if prop_place_holder in import_path:
913 import_path = import_path.replace(prop_place_holder, value)
914 if '$' in import_path:
915 logger.info('Unresolved place holder in import path %s', import_path)
916 return {}
917
918 import_path = import_path.replace('/{}'.format(self.partition),
919 self.partition.upper())
920 logger.info('Parsing build props override from %s', import_path)
921
922 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
923 d = LoadDictionaryFromLines(lines)
924 return {key: val for key, val in d.items()
925 if key in self.props_allow_override}
926
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000927 def GetProp(self, prop):
928 return self.build_props.get(prop)
929
930
Tianjie Xucfa86222016-03-07 16:31:19 -0800931def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
932 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700933 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700934 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700935 self.mount_point = mount_point
936 self.fs_type = fs_type
937 self.device = device
938 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700939 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700940 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700941
942 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800943 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700944 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700945 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700946 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700947
Tao Baod1de6f32017-03-01 16:38:48 -0800948 assert fstab_version == 2
949
950 d = {}
951 for line in data.split("\n"):
952 line = line.strip()
953 if not line or line.startswith("#"):
954 continue
955
956 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
957 pieces = line.split()
958 if len(pieces) != 5:
959 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
960
961 # Ignore entries that are managed by vold.
962 options = pieces[4]
963 if "voldmanaged=" in options:
964 continue
965
966 # It's a good line, parse it.
967 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700968 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800969 options = options.split(",")
970 for i in options:
971 if i.startswith("length="):
972 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700973 elif i == "slotselect":
974 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800975 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800976 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700977 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800978
Tao Baod1de6f32017-03-01 16:38:48 -0800979 mount_flags = pieces[3]
980 # Honor the SELinux context if present.
981 context = None
982 for i in mount_flags.split(","):
983 if i.startswith("context="):
984 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800985
Tao Baod1de6f32017-03-01 16:38:48 -0800986 mount_point = pieces[1]
987 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700988 device=pieces[0], length=length, context=context,
989 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800990
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700991 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700992 # system. Other areas assume system is always at "/system" so point /system
993 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700994 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800995 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700996 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700997 return d
998
999
Tao Bao765668f2019-10-04 22:03:00 -07001000def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1001 """Finds the path to recovery fstab and loads its contents."""
1002 # recovery fstab is only meaningful when installing an update via recovery
1003 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001004 if info_dict.get('ab_update') == 'true' and \
1005 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001006 return None
1007
1008 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1009 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1010 # cases, since it may load the info_dict from an old build (e.g. when
1011 # generating incremental OTAs from that build).
1012 system_root_image = info_dict.get('system_root_image') == 'true'
1013 if info_dict.get('no_recovery') != 'true':
1014 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1015 if isinstance(input_file, zipfile.ZipFile):
1016 if recovery_fstab_path not in input_file.namelist():
1017 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1018 else:
1019 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1020 if not os.path.exists(path):
1021 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1022 return LoadRecoveryFSTab(
1023 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1024 system_root_image)
1025
1026 if info_dict.get('recovery_as_boot') == 'true':
1027 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1028 if isinstance(input_file, zipfile.ZipFile):
1029 if recovery_fstab_path not in input_file.namelist():
1030 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1031 else:
1032 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1033 if not os.path.exists(path):
1034 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1035 return LoadRecoveryFSTab(
1036 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1037 system_root_image)
1038
1039 return None
1040
1041
Doug Zongker37974732010-09-16 17:44:38 -07001042def DumpInfoDict(d):
1043 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001044 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001045
Dan Albert8b72aef2015-03-23 19:13:21 -07001046
Daniel Norman55417142019-11-25 16:04:36 -08001047def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001048 """Merges dynamic partition info variables.
1049
1050 Args:
1051 framework_dict: The dictionary of dynamic partition info variables from the
1052 partial framework target files.
1053 vendor_dict: The dictionary of dynamic partition info variables from the
1054 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001055
1056 Returns:
1057 The merged dynamic partition info dictionary.
1058 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001059
1060 def uniq_concat(a, b):
1061 combined = set(a.split(" "))
1062 combined.update(set(b.split(" ")))
1063 combined = [item.strip() for item in combined if item.strip()]
1064 return " ".join(sorted(combined))
1065
1066 if (framework_dict.get("use_dynamic_partitions") !=
1067 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1068 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1069
1070 merged_dict = {"use_dynamic_partitions": "true"}
1071
1072 merged_dict["dynamic_partition_list"] = uniq_concat(
1073 framework_dict.get("dynamic_partition_list", ""),
1074 vendor_dict.get("dynamic_partition_list", ""))
1075
1076 # Super block devices are defined by the vendor dict.
1077 if "super_block_devices" in vendor_dict:
1078 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1079 for block_device in merged_dict["super_block_devices"].split(" "):
1080 key = "super_%s_device_size" % block_device
1081 if key not in vendor_dict:
1082 raise ValueError("Vendor dict does not contain required key %s." % key)
1083 merged_dict[key] = vendor_dict[key]
1084
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 # Partition groups and group sizes are defined by the vendor dict because
1086 # these values may vary for each board that uses a shared system image.
1087 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001088 for partition_group in merged_dict["super_partition_groups"].split(" "):
1089 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001090 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001091 if key not in vendor_dict:
1092 raise ValueError("Vendor dict does not contain required key %s." % key)
1093 merged_dict[key] = vendor_dict[key]
1094
1095 # Set the partition group's partition list using a concatenation of the
1096 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001097 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001098 merged_dict[key] = uniq_concat(
1099 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301100
Daniel Normanb0c75912020-09-24 14:30:21 -07001101 # Various other flags should be copied from the vendor dict, if defined.
1102 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1103 "super_metadata_device", "super_partition_error_limit",
1104 "super_partition_size"):
1105 if key in vendor_dict.keys():
1106 merged_dict[key] = vendor_dict[key]
1107
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001108 return merged_dict
1109
1110
Daniel Norman21c34f72020-11-11 17:25:50 -08001111def PartitionMapFromTargetFiles(target_files_dir):
1112 """Builds a map from partition -> path within an extracted target files directory."""
1113 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1114 possible_subdirs = {
1115 "system": ["SYSTEM"],
1116 "vendor": ["VENDOR", "SYSTEM/vendor"],
1117 "product": ["PRODUCT", "SYSTEM/product"],
1118 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1119 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1120 "vendor_dlkm": [
1121 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1122 ],
1123 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1124 }
1125 partition_map = {}
1126 for partition, subdirs in possible_subdirs.items():
1127 for subdir in subdirs:
1128 if os.path.exists(os.path.join(target_files_dir, subdir)):
1129 partition_map[partition] = subdir
1130 break
1131 return partition_map
1132
1133
Daniel Normand3351562020-10-29 12:33:11 -07001134def SharedUidPartitionViolations(uid_dict, partition_groups):
1135 """Checks for APK sharedUserIds that cross partition group boundaries.
1136
1137 This uses a single or merged build's shareduid_violation_modules.json
1138 output file, as generated by find_shareduid_violation.py or
1139 core/tasks/find-shareduid-violation.mk.
1140
1141 An error is defined as a sharedUserId that is found in a set of partitions
1142 that span more than one partition group.
1143
1144 Args:
1145 uid_dict: A dictionary created by using the standard json module to read a
1146 complete shareduid_violation_modules.json file.
1147 partition_groups: A list of groups, where each group is a list of
1148 partitions.
1149
1150 Returns:
1151 A list of error messages.
1152 """
1153 errors = []
1154 for uid, partitions in uid_dict.items():
1155 found_in_groups = [
1156 group for group in partition_groups
1157 if set(partitions.keys()) & set(group)
1158 ]
1159 if len(found_in_groups) > 1:
1160 errors.append(
1161 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1162 % (uid, ",".join(sorted(partitions.keys()))))
1163 return errors
1164
1165
Daniel Norman21c34f72020-11-11 17:25:50 -08001166def RunHostInitVerifier(product_out, partition_map):
1167 """Runs host_init_verifier on the init rc files within partitions.
1168
1169 host_init_verifier searches the etc/init path within each partition.
1170
1171 Args:
1172 product_out: PRODUCT_OUT directory, containing partition directories.
1173 partition_map: A map of partition name -> relative path within product_out.
1174 """
1175 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1176 cmd = ["host_init_verifier"]
1177 for partition, path in partition_map.items():
1178 if partition not in allowed_partitions:
1179 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1180 partition)
1181 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1182 # Add --property-contexts if the file exists on the partition.
1183 property_contexts = "%s_property_contexts" % (
1184 "plat" if partition == "system" else partition)
1185 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1186 property_contexts)
1187 if os.path.exists(property_contexts_path):
1188 cmd.append("--property-contexts=%s" % property_contexts_path)
1189 # Add the passwd file if the file exists on the partition.
1190 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1191 if os.path.exists(passwd_path):
1192 cmd.extend(["-p", passwd_path])
1193 return RunAndCheckOutput(cmd)
1194
1195
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001196def AppendAVBSigningArgs(cmd, partition):
1197 """Append signing arguments for avbtool."""
1198 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1199 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001200 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1201 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1202 if os.path.exists(new_key_path):
1203 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001204 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1205 if key_path and algorithm:
1206 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001207 avb_salt = OPTIONS.info_dict.get("avb_salt")
1208 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001209 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001210 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001211
1212
Tao Bao765668f2019-10-04 22:03:00 -07001213def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001214 """Returns the VBMeta arguments for partition.
1215
1216 It sets up the VBMeta argument by including the partition descriptor from the
1217 given 'image', or by configuring the partition as a chained partition.
1218
1219 Args:
1220 partition: The name of the partition (e.g. "system").
1221 image: The path to the partition image.
1222 info_dict: A dict returned by common.LoadInfoDict(). Will use
1223 OPTIONS.info_dict if None has been given.
1224
1225 Returns:
1226 A list of VBMeta arguments.
1227 """
1228 if info_dict is None:
1229 info_dict = OPTIONS.info_dict
1230
1231 # Check if chain partition is used.
1232 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001233 if not key_path:
1234 return ["--include_descriptors_from_image", image]
1235
1236 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1237 # into vbmeta.img. The recovery image will be configured on an independent
1238 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1239 # See details at
1240 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001241 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001242 return []
1243
1244 # Otherwise chain the partition into vbmeta.
1245 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1246 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001247
1248
Tao Bao02a08592018-07-22 12:40:45 -07001249def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1250 """Constructs and returns the arg to build or verify a chained partition.
1251
1252 Args:
1253 partition: The partition name.
1254 info_dict: The info dict to look up the key info and rollback index
1255 location.
1256 key: The key to be used for building or verifying the partition. Defaults to
1257 the key listed in info_dict.
1258
1259 Returns:
1260 A string of form "partition:rollback_index_location:key" that can be used to
1261 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001262 """
1263 if key is None:
1264 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001265 if key and not os.path.exists(key) and OPTIONS.search_path:
1266 new_key_path = os.path.join(OPTIONS.search_path, key)
1267 if os.path.exists(new_key_path):
1268 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001269 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001270 rollback_index_location = info_dict[
1271 "avb_" + partition + "_rollback_index_location"]
1272 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1273
1274
Tianjie20dd8f22020-04-19 15:51:16 -07001275def ConstructAftlMakeImageCommands(output_image):
1276 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001277
1278 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001279 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001280 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1281 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1282 'No AFTL manufacturer key provided.'
1283
1284 vbmeta_image = MakeTempFile()
1285 os.rename(output_image, vbmeta_image)
1286 build_info = BuildInfo(OPTIONS.info_dict)
1287 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001288 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001289 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001290 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001291 "--vbmeta_image_path", vbmeta_image,
1292 "--output", output_image,
1293 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001294 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001295 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1296 "--algorithm", "SHA256_RSA4096",
1297 "--padding", "4096"]
1298 if OPTIONS.aftl_signer_helper:
1299 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001300 return aftl_cmd
1301
1302
1303def AddAftlInclusionProof(output_image):
1304 """Appends the aftl inclusion proof to the vbmeta image."""
1305
1306 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001307 RunAndCheckOutput(aftl_cmd)
1308
1309 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1310 output_image, '--transparency_log_pub_keys',
1311 OPTIONS.aftl_key_path]
1312 RunAndCheckOutput(verify_cmd)
1313
1314
Daniel Norman276f0622019-07-26 14:13:51 -07001315def BuildVBMeta(image_path, partitions, name, needed_partitions):
1316 """Creates a VBMeta image.
1317
1318 It generates the requested VBMeta image. The requested image could be for
1319 top-level or chained VBMeta image, which is determined based on the name.
1320
1321 Args:
1322 image_path: The output path for the new VBMeta image.
1323 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001324 values. Only valid partition names are accepted, as partitions listed
1325 in common.AVB_PARTITIONS and custom partitions listed in
1326 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001327 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1328 needed_partitions: Partitions whose descriptors should be included into the
1329 generated VBMeta image.
1330
1331 Raises:
1332 AssertionError: On invalid input args.
1333 """
1334 avbtool = OPTIONS.info_dict["avb_avbtool"]
1335 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1336 AppendAVBSigningArgs(cmd, name)
1337
Hongguang Chenf23364d2020-04-27 18:36:36 -07001338 custom_partitions = OPTIONS.info_dict.get(
1339 "avb_custom_images_partition_list", "").strip().split()
1340
Daniel Norman276f0622019-07-26 14:13:51 -07001341 for partition, path in partitions.items():
1342 if partition not in needed_partitions:
1343 continue
1344 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001345 partition in AVB_VBMETA_PARTITIONS or
1346 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001347 'Unknown partition: {}'.format(partition)
1348 assert os.path.exists(path), \
1349 'Failed to find {} for {}'.format(path, partition)
1350 cmd.extend(GetAvbPartitionArg(partition, path))
1351
1352 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1353 if args and args.strip():
1354 split_args = shlex.split(args)
1355 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001356 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001357 # as a path relative to source tree, which may not be available at the
1358 # same location when running this script (we have the input target_files
1359 # zip only). For such cases, we additionally scan other locations (e.g.
1360 # IMAGES/, RADIO/, etc) before bailing out.
1361 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001362 chained_image = split_args[index + 1]
1363 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001364 continue
1365 found = False
1366 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1367 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001368 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001369 if os.path.exists(alt_path):
1370 split_args[index + 1] = alt_path
1371 found = True
1372 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001373 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001374 cmd.extend(split_args)
1375
1376 RunAndCheckOutput(cmd)
1377
Tianjie Xueaed60c2020-03-12 00:33:28 -07001378 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001379 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001380 AddAftlInclusionProof(image_path)
1381
Daniel Norman276f0622019-07-26 14:13:51 -07001382
J. Avila98cd4cc2020-06-10 20:09:10 +00001383def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001384 ramdisk_img = tempfile.NamedTemporaryFile()
1385
1386 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1387 cmd = ["mkbootfs", "-f", fs_config_file,
1388 os.path.join(sourcedir, "RAMDISK")]
1389 else:
1390 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1391 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001392 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001393 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001394 stdout=ramdisk_img.file.fileno())
1395 else:
1396 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001397
1398 p2.wait()
1399 p1.wait()
1400 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001401 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001402
1403 return ramdisk_img
1404
1405
Steve Muckle9793cf62020-04-08 18:27:00 -07001406def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001407 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001408 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001409
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001410 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001411 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1412 we are building a two-step special image (i.e. building a recovery image to
1413 be loaded into /boot in two-step OTAs).
1414
1415 Return the image data, or None if sourcedir does not appear to contains files
1416 for building the requested image.
1417 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001418
Yifan Hong63c5ca12020-10-08 11:54:02 -07001419 if info_dict is None:
1420 info_dict = OPTIONS.info_dict
1421
Steve Muckle9793cf62020-04-08 18:27:00 -07001422 # "boot" or "recovery", without extension.
1423 partition_name = os.path.basename(sourcedir).lower()
1424
Yifan Hong63c5ca12020-10-08 11:54:02 -07001425 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001426 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001427 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1428 logger.info("Excluded kernel binary from recovery image.")
1429 else:
1430 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001431 else:
1432 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001433 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001434 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001435 return None
1436
1437 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001438 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001439
Doug Zongkereef39442009-04-02 12:14:19 -07001440 img = tempfile.NamedTemporaryFile()
1441
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001442 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001443 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1444 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001445
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001446 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1447 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1448
Yifan Hong63c5ca12020-10-08 11:54:02 -07001449 cmd = [mkbootimg]
1450 if kernel:
1451 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001452
Benoit Fradina45a8682014-07-14 21:00:43 +02001453 fn = os.path.join(sourcedir, "second")
1454 if os.access(fn, os.F_OK):
1455 cmd.append("--second")
1456 cmd.append(fn)
1457
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001458 fn = os.path.join(sourcedir, "dtb")
1459 if os.access(fn, os.F_OK):
1460 cmd.append("--dtb")
1461 cmd.append(fn)
1462
Doug Zongker171f1cd2009-06-15 22:36:37 -07001463 fn = os.path.join(sourcedir, "cmdline")
1464 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001465 cmd.append("--cmdline")
1466 cmd.append(open(fn).read().rstrip("\n"))
1467
1468 fn = os.path.join(sourcedir, "base")
1469 if os.access(fn, os.F_OK):
1470 cmd.append("--base")
1471 cmd.append(open(fn).read().rstrip("\n"))
1472
Ying Wang4de6b5b2010-08-25 14:29:34 -07001473 fn = os.path.join(sourcedir, "pagesize")
1474 if os.access(fn, os.F_OK):
1475 cmd.append("--pagesize")
1476 cmd.append(open(fn).read().rstrip("\n"))
1477
Steve Mucklef84668e2020-03-16 19:13:46 -07001478 if partition_name == "recovery":
1479 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301480 if not args:
1481 # Fall back to "mkbootimg_args" for recovery image
1482 # in case "recovery_mkbootimg_args" is not set.
1483 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001484 else:
1485 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001486 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001487 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001488
Tao Bao76def242017-11-21 09:25:31 -08001489 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001490 if args and args.strip():
1491 cmd.extend(shlex.split(args))
1492
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001493 if has_ramdisk:
1494 cmd.extend(["--ramdisk", ramdisk_img.name])
1495
Tao Baod95e9fd2015-03-29 23:07:41 -07001496 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001497 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001498 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001499 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001500 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001501 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001502
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001503 if partition_name == "recovery":
1504 if info_dict.get("include_recovery_dtbo") == "true":
1505 fn = os.path.join(sourcedir, "recovery_dtbo")
1506 cmd.extend(["--recovery_dtbo", fn])
1507 if info_dict.get("include_recovery_acpio") == "true":
1508 fn = os.path.join(sourcedir, "recovery_acpio")
1509 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001510
Tao Bao986ee862018-10-04 15:46:16 -07001511 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001512
Tao Bao76def242017-11-21 09:25:31 -08001513 if (info_dict.get("boot_signer") == "true" and
1514 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001515 # Hard-code the path as "/boot" for two-step special recovery image (which
1516 # will be loaded into /boot during the two-step OTA).
1517 if two_step_image:
1518 path = "/boot"
1519 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001520 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001521 cmd = [OPTIONS.boot_signer_path]
1522 cmd.extend(OPTIONS.boot_signer_args)
1523 cmd.extend([path, img.name,
1524 info_dict["verity_key"] + ".pk8",
1525 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001526 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001527
Tao Baod95e9fd2015-03-29 23:07:41 -07001528 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001529 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001530 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001531 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001532 # We have switched from the prebuilt futility binary to using the tool
1533 # (futility-host) built from the source. Override the setting in the old
1534 # TF.zip.
1535 futility = info_dict["futility"]
1536 if futility.startswith("prebuilts/"):
1537 futility = "futility-host"
1538 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001539 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001540 info_dict["vboot_key"] + ".vbprivk",
1541 info_dict["vboot_subkey"] + ".vbprivk",
1542 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001543 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001544 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001545
Tao Baof3282b42015-04-01 11:21:55 -07001546 # Clean up the temp files.
1547 img_unsigned.close()
1548 img_keyblock.close()
1549
David Zeuthen8fecb282017-12-01 16:24:01 -05001550 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001551 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001552 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001553 if partition_name == "recovery":
1554 part_size = info_dict["recovery_size"]
1555 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001556 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001557 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001558 "--partition_size", str(part_size), "--partition_name",
1559 partition_name]
1560 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001561 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001562 if args and args.strip():
1563 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001564 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001565
1566 img.seek(os.SEEK_SET, 0)
1567 data = img.read()
1568
1569 if has_ramdisk:
1570 ramdisk_img.close()
1571 img.close()
1572
1573 return data
1574
1575
Doug Zongkerd5131602012-08-02 14:46:42 -07001576def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001577 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001578 """Return a File object with the desired bootable image.
1579
1580 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1581 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1582 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001583
Doug Zongker55d93282011-01-25 17:03:34 -08001584 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1585 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001586 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001587 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001588
1589 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1590 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001591 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001592 return File.FromLocalFile(name, prebuilt_path)
1593
Tao Bao32fcdab2018-10-12 10:30:39 -07001594 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001595
1596 if info_dict is None:
1597 info_dict = OPTIONS.info_dict
1598
1599 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001600 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1601 # for recovery.
1602 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1603 prebuilt_name != "boot.img" or
1604 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001605
Doug Zongker6f1d0312014-08-22 08:07:12 -07001606 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001607 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001608 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001609 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001610 if data:
1611 return File(name, data)
1612 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001613
Doug Zongkereef39442009-04-02 12:14:19 -07001614
Steve Mucklee1b10862019-07-10 10:49:37 -07001615def _BuildVendorBootImage(sourcedir, info_dict=None):
1616 """Build a vendor boot image from the specified sourcedir.
1617
1618 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1619 turn them into a vendor boot image.
1620
1621 Return the image data, or None if sourcedir does not appear to contains files
1622 for building the requested image.
1623 """
1624
1625 if info_dict is None:
1626 info_dict = OPTIONS.info_dict
1627
1628 img = tempfile.NamedTemporaryFile()
1629
J. Avila98cd4cc2020-06-10 20:09:10 +00001630 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1631 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001632
1633 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1634 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1635
1636 cmd = [mkbootimg]
1637
1638 fn = os.path.join(sourcedir, "dtb")
1639 if os.access(fn, os.F_OK):
1640 cmd.append("--dtb")
1641 cmd.append(fn)
1642
1643 fn = os.path.join(sourcedir, "vendor_cmdline")
1644 if os.access(fn, os.F_OK):
1645 cmd.append("--vendor_cmdline")
1646 cmd.append(open(fn).read().rstrip("\n"))
1647
1648 fn = os.path.join(sourcedir, "base")
1649 if os.access(fn, os.F_OK):
1650 cmd.append("--base")
1651 cmd.append(open(fn).read().rstrip("\n"))
1652
1653 fn = os.path.join(sourcedir, "pagesize")
1654 if os.access(fn, os.F_OK):
1655 cmd.append("--pagesize")
1656 cmd.append(open(fn).read().rstrip("\n"))
1657
1658 args = info_dict.get("mkbootimg_args")
1659 if args and args.strip():
1660 cmd.extend(shlex.split(args))
1661
1662 args = info_dict.get("mkbootimg_version_args")
1663 if args and args.strip():
1664 cmd.extend(shlex.split(args))
1665
1666 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1667 cmd.extend(["--vendor_boot", img.name])
1668
1669 RunAndCheckOutput(cmd)
1670
1671 # AVB: if enabled, calculate and add hash.
1672 if info_dict.get("avb_enable") == "true":
1673 avbtool = info_dict["avb_avbtool"]
1674 part_size = info_dict["vendor_boot_size"]
1675 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001676 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001677 AppendAVBSigningArgs(cmd, "vendor_boot")
1678 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1679 if args and args.strip():
1680 cmd.extend(shlex.split(args))
1681 RunAndCheckOutput(cmd)
1682
1683 img.seek(os.SEEK_SET, 0)
1684 data = img.read()
1685
1686 ramdisk_img.close()
1687 img.close()
1688
1689 return data
1690
1691
1692def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1693 info_dict=None):
1694 """Return a File object with the desired vendor boot image.
1695
1696 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1697 the source files in 'unpack_dir'/'tree_subdir'."""
1698
1699 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1700 if os.path.exists(prebuilt_path):
1701 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1702 return File.FromLocalFile(name, prebuilt_path)
1703
1704 logger.info("building image from target_files %s...", tree_subdir)
1705
1706 if info_dict is None:
1707 info_dict = OPTIONS.info_dict
1708
Kelvin Zhang0876c412020-06-23 15:06:58 -04001709 data = _BuildVendorBootImage(
1710 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001711 if data:
1712 return File(name, data)
1713 return None
1714
1715
Narayan Kamatha07bf042017-08-14 14:49:21 +01001716def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001717 """Gunzips the given gzip compressed file to a given output file."""
1718 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001719 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001720 shutil.copyfileobj(in_file, out_file)
1721
1722
Tao Bao0ff15de2019-03-20 11:26:06 -07001723def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001724 """Unzips the archive to the given directory.
1725
1726 Args:
1727 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001728 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001729 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1730 archvie. Non-matching patterns will be filtered out. If there's no match
1731 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001732 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001733 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001734 if patterns is not None:
1735 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001736 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001737 names = input_zip.namelist()
1738 filtered = [
1739 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1740
1741 # There isn't any matching files. Don't unzip anything.
1742 if not filtered:
1743 return
1744 cmd.extend(filtered)
1745
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001746 RunAndCheckOutput(cmd)
1747
1748
Doug Zongker75f17362009-12-08 13:46:44 -08001749def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001750 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001751
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001752 Args:
1753 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1754 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1755
1756 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1757 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001758
Tao Bao1c830bf2017-12-25 10:43:47 -08001759 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001760 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001761 """
Doug Zongkereef39442009-04-02 12:14:19 -07001762
Tao Bao1c830bf2017-12-25 10:43:47 -08001763 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001764 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1765 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001766 UnzipToDir(m.group(1), tmp, pattern)
1767 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001768 filename = m.group(1)
1769 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001770 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001771
Tao Baodba59ee2018-01-09 13:21:02 -08001772 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001773
1774
Yifan Hong8a66a712019-04-04 15:37:57 -07001775def GetUserImage(which, tmpdir, input_zip,
1776 info_dict=None,
1777 allow_shared_blocks=None,
1778 hashtree_info_generator=None,
1779 reset_file_map=False):
1780 """Returns an Image object suitable for passing to BlockImageDiff.
1781
1782 This function loads the specified image from the given path. If the specified
1783 image is sparse, it also performs additional processing for OTA purpose. For
1784 example, it always adds block 0 to clobbered blocks list. It also detects
1785 files that cannot be reconstructed from the block list, for whom we should
1786 avoid applying imgdiff.
1787
1788 Args:
1789 which: The partition name.
1790 tmpdir: The directory that contains the prebuilt image and block map file.
1791 input_zip: The target-files ZIP archive.
1792 info_dict: The dict to be looked up for relevant info.
1793 allow_shared_blocks: If image is sparse, whether having shared blocks is
1794 allowed. If none, it is looked up from info_dict.
1795 hashtree_info_generator: If present and image is sparse, generates the
1796 hashtree_info for this sparse image.
1797 reset_file_map: If true and image is sparse, reset file map before returning
1798 the image.
1799 Returns:
1800 A Image object. If it is a sparse image and reset_file_map is False, the
1801 image will have file_map info loaded.
1802 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001803 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001804 info_dict = LoadInfoDict(input_zip)
1805
1806 is_sparse = info_dict.get("extfs_sparse_flag")
1807
1808 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1809 # shared blocks (i.e. some blocks will show up in multiple files' block
1810 # list). We can only allocate such shared blocks to the first "owner", and
1811 # disable imgdiff for all later occurrences.
1812 if allow_shared_blocks is None:
1813 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1814
1815 if is_sparse:
1816 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1817 hashtree_info_generator)
1818 if reset_file_map:
1819 img.ResetFileMap()
1820 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001821 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001822
1823
1824def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1825 """Returns a Image object suitable for passing to BlockImageDiff.
1826
1827 This function loads the specified non-sparse image from the given path.
1828
1829 Args:
1830 which: The partition name.
1831 tmpdir: The directory that contains the prebuilt image and block map file.
1832 Returns:
1833 A Image object.
1834 """
1835 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1836 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1837
1838 # The image and map files must have been created prior to calling
1839 # ota_from_target_files.py (since LMP).
1840 assert os.path.exists(path) and os.path.exists(mappath)
1841
Tianjie Xu41976c72019-07-03 13:57:01 -07001842 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1843
Yifan Hong8a66a712019-04-04 15:37:57 -07001844
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001845def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1846 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001847 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1848
1849 This function loads the specified sparse image from the given path, and
1850 performs additional processing for OTA purpose. For example, it always adds
1851 block 0 to clobbered blocks list. It also detects files that cannot be
1852 reconstructed from the block list, for whom we should avoid applying imgdiff.
1853
1854 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001855 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001856 tmpdir: The directory that contains the prebuilt image and block map file.
1857 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001858 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001859 hashtree_info_generator: If present, generates the hashtree_info for this
1860 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001861 Returns:
1862 A SparseImage object, with file_map info loaded.
1863 """
Tao Baoc765cca2018-01-31 17:32:40 -08001864 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1865 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1866
1867 # The image and map files must have been created prior to calling
1868 # ota_from_target_files.py (since LMP).
1869 assert os.path.exists(path) and os.path.exists(mappath)
1870
1871 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1872 # it to clobbered_blocks so that it will be written to the target
1873 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1874 clobbered_blocks = "0"
1875
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001876 image = sparse_img.SparseImage(
1877 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1878 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001879
1880 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1881 # if they contain all zeros. We can't reconstruct such a file from its block
1882 # list. Tag such entries accordingly. (Bug: 65213616)
1883 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001884 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001885 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001886 continue
1887
Tom Cherryd14b8952018-08-09 14:26:00 -07001888 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1889 # filename listed in system.map may contain an additional leading slash
1890 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1891 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001892 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001893
Tom Cherryd14b8952018-08-09 14:26:00 -07001894 # Special handling another case, where files not under /system
1895 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001896 if which == 'system' and not arcname.startswith('SYSTEM'):
1897 arcname = 'ROOT/' + arcname
1898
1899 assert arcname in input_zip.namelist(), \
1900 "Failed to find the ZIP entry for {}".format(entry)
1901
Tao Baoc765cca2018-01-31 17:32:40 -08001902 info = input_zip.getinfo(arcname)
1903 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001904
1905 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001906 # image, check the original block list to determine its completeness. Note
1907 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001908 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001909 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001910
Tao Baoc765cca2018-01-31 17:32:40 -08001911 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1912 ranges.extra['incomplete'] = True
1913
1914 return image
1915
1916
Doug Zongkereef39442009-04-02 12:14:19 -07001917def GetKeyPasswords(keylist):
1918 """Given a list of keys, prompt the user to enter passwords for
1919 those which require them. Return a {key: password} dict. password
1920 will be None if the key has no password."""
1921
Doug Zongker8ce7c252009-05-22 13:34:54 -07001922 no_passwords = []
1923 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001924 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001925 devnull = open("/dev/null", "w+b")
1926 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001927 # We don't need a password for things that aren't really keys.
1928 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001929 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001930 continue
1931
T.R. Fullhart37e10522013-03-18 10:31:26 -07001932 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001933 "-inform", "DER", "-nocrypt"],
1934 stdin=devnull.fileno(),
1935 stdout=devnull.fileno(),
1936 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001937 p.communicate()
1938 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001939 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001940 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001941 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001942 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1943 "-inform", "DER", "-passin", "pass:"],
1944 stdin=devnull.fileno(),
1945 stdout=devnull.fileno(),
1946 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001947 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001948 if p.returncode == 0:
1949 # Encrypted key with empty string as password.
1950 key_passwords[k] = ''
1951 elif stderr.startswith('Error decrypting key'):
1952 # Definitely encrypted key.
1953 # It would have said "Error reading key" if it didn't parse correctly.
1954 need_passwords.append(k)
1955 else:
1956 # Potentially, a type of key that openssl doesn't understand.
1957 # We'll let the routines in signapk.jar handle it.
1958 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001959 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001960
T.R. Fullhart37e10522013-03-18 10:31:26 -07001961 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001962 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001963 return key_passwords
1964
1965
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001966def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001967 """Gets the minSdkVersion declared in the APK.
1968
changho.shin0f125362019-07-08 10:59:00 +09001969 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001970 This can be both a decimal number (API Level) or a codename.
1971
1972 Args:
1973 apk_name: The APK filename.
1974
1975 Returns:
1976 The parsed SDK version string.
1977
1978 Raises:
1979 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001980 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001981 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001982 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001983 stderr=subprocess.PIPE)
1984 stdoutdata, stderrdata = proc.communicate()
1985 if proc.returncode != 0:
1986 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001987 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001988 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001989
Tao Baof47bf0f2018-03-21 23:28:51 -07001990 for line in stdoutdata.split("\n"):
1991 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001992 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1993 if m:
1994 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001995 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001996
1997
1998def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001999 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002000
Tao Baof47bf0f2018-03-21 23:28:51 -07002001 If minSdkVersion is set to a codename, it is translated to a number using the
2002 provided map.
2003
2004 Args:
2005 apk_name: The APK filename.
2006
2007 Returns:
2008 The parsed SDK version number.
2009
2010 Raises:
2011 ExternalError: On failing to get the min SDK version number.
2012 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002013 version = GetMinSdkVersion(apk_name)
2014 try:
2015 return int(version)
2016 except ValueError:
2017 # Not a decimal number. Codename?
2018 if version in codename_to_api_level_map:
2019 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002020 raise ExternalError(
2021 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2022 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002023
2024
2025def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002026 codename_to_api_level_map=None, whole_file=False,
2027 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002028 """Sign the input_name zip/jar/apk, producing output_name. Use the
2029 given key and password (the latter may be None if the key does not
2030 have a password.
2031
Doug Zongker951495f2009-08-14 12:44:19 -07002032 If whole_file is true, use the "-w" option to SignApk to embed a
2033 signature that covers the whole file in the archive comment of the
2034 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002035
2036 min_api_level is the API Level (int) of the oldest platform this file may end
2037 up on. If not specified for an APK, the API Level is obtained by interpreting
2038 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2039
2040 codename_to_api_level_map is needed to translate the codename which may be
2041 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002042
2043 Caller may optionally specify extra args to be passed to SignApk, which
2044 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002045 """
Tao Bao76def242017-11-21 09:25:31 -08002046 if codename_to_api_level_map is None:
2047 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002048 if extra_signapk_args is None:
2049 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002050
Alex Klyubin9667b182015-12-10 13:38:50 -08002051 java_library_path = os.path.join(
2052 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2053
Tao Baoe95540e2016-11-08 12:08:53 -08002054 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2055 ["-Djava.library.path=" + java_library_path,
2056 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002057 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002058 if whole_file:
2059 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002060
2061 min_sdk_version = min_api_level
2062 if min_sdk_version is None:
2063 if not whole_file:
2064 min_sdk_version = GetMinSdkVersionInt(
2065 input_name, codename_to_api_level_map)
2066 if min_sdk_version is not None:
2067 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2068
T.R. Fullhart37e10522013-03-18 10:31:26 -07002069 cmd.extend([key + OPTIONS.public_key_suffix,
2070 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002071 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002072
Tao Bao73dd4f42018-10-04 16:25:33 -07002073 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002074 if password is not None:
2075 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002076 stdoutdata, _ = proc.communicate(password)
2077 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002078 raise ExternalError(
2079 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002080 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002081
Doug Zongkereef39442009-04-02 12:14:19 -07002082
Doug Zongker37974732010-09-16 17:44:38 -07002083def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002084 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002085
Tao Bao9dd909e2017-11-14 11:27:32 -08002086 For non-AVB images, raise exception if the data is too big. Print a warning
2087 if the data is nearing the maximum size.
2088
2089 For AVB images, the actual image size should be identical to the limit.
2090
2091 Args:
2092 data: A string that contains all the data for the partition.
2093 target: The partition name. The ".img" suffix is optional.
2094 info_dict: The dict to be looked up for relevant info.
2095 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002096 if target.endswith(".img"):
2097 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002098 mount_point = "/" + target
2099
Ying Wangf8824af2014-06-03 14:07:27 -07002100 fs_type = None
2101 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002102 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002103 if mount_point == "/userdata":
2104 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002105 p = info_dict["fstab"][mount_point]
2106 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002107 device = p.device
2108 if "/" in device:
2109 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002110 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002111 if not fs_type or not limit:
2112 return
Doug Zongkereef39442009-04-02 12:14:19 -07002113
Andrew Boie0f9aec82012-02-14 09:32:52 -08002114 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002115 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2116 # path.
2117 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2118 if size != limit:
2119 raise ExternalError(
2120 "Mismatching image size for %s: expected %d actual %d" % (
2121 target, limit, size))
2122 else:
2123 pct = float(size) * 100.0 / limit
2124 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2125 if pct >= 99.0:
2126 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002127
2128 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002129 logger.warning("\n WARNING: %s\n", msg)
2130 else:
2131 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002132
2133
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002134def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002135 """Parses the APK certs info from a given target-files zip.
2136
2137 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2138 tuple with the following elements: (1) a dictionary that maps packages to
2139 certs (based on the "certificate" and "private_key" attributes in the file;
2140 (2) a string representing the extension of compressed APKs in the target files
2141 (e.g ".gz", ".bro").
2142
2143 Args:
2144 tf_zip: The input target_files ZipFile (already open).
2145
2146 Returns:
2147 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2148 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2149 no compressed APKs.
2150 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002151 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002152 compressed_extension = None
2153
Tao Bao0f990332017-09-08 19:02:54 -07002154 # META/apkcerts.txt contains the info for _all_ the packages known at build
2155 # time. Filter out the ones that are not installed.
2156 installed_files = set()
2157 for name in tf_zip.namelist():
2158 basename = os.path.basename(name)
2159 if basename:
2160 installed_files.add(basename)
2161
Tao Baoda30cfa2017-12-01 16:19:46 -08002162 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002163 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002164 if not line:
2165 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002166 m = re.match(
2167 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002168 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2169 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002170 line)
2171 if not m:
2172 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002173
Tao Bao818ddf52018-01-05 11:17:34 -08002174 matches = m.groupdict()
2175 cert = matches["CERT"]
2176 privkey = matches["PRIVKEY"]
2177 name = matches["NAME"]
2178 this_compressed_extension = matches["COMPRESSED"]
2179
2180 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2181 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2182 if cert in SPECIAL_CERT_STRINGS and not privkey:
2183 certmap[name] = cert
2184 elif (cert.endswith(OPTIONS.public_key_suffix) and
2185 privkey.endswith(OPTIONS.private_key_suffix) and
2186 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2187 certmap[name] = cert[:-public_key_suffix_len]
2188 else:
2189 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2190
2191 if not this_compressed_extension:
2192 continue
2193
2194 # Only count the installed files.
2195 filename = name + '.' + this_compressed_extension
2196 if filename not in installed_files:
2197 continue
2198
2199 # Make sure that all the values in the compression map have the same
2200 # extension. We don't support multiple compression methods in the same
2201 # system image.
2202 if compressed_extension:
2203 if this_compressed_extension != compressed_extension:
2204 raise ValueError(
2205 "Multiple compressed extensions: {} vs {}".format(
2206 compressed_extension, this_compressed_extension))
2207 else:
2208 compressed_extension = this_compressed_extension
2209
2210 return (certmap,
2211 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002212
2213
Doug Zongkereef39442009-04-02 12:14:19 -07002214COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002215Global options
2216
2217 -p (--path) <dir>
2218 Prepend <dir>/bin to the list of places to search for binaries run by this
2219 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002220
Doug Zongker05d3dea2009-06-22 11:32:31 -07002221 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002222 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002223
Tao Bao30df8b42018-04-23 15:32:53 -07002224 -x (--extra) <key=value>
2225 Add a key/value pair to the 'extras' dict, which device-specific extension
2226 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002227
Doug Zongkereef39442009-04-02 12:14:19 -07002228 -v (--verbose)
2229 Show command lines being executed.
2230
2231 -h (--help)
2232 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002233
2234 --logfile <file>
2235 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002236"""
2237
Kelvin Zhang0876c412020-06-23 15:06:58 -04002238
Doug Zongkereef39442009-04-02 12:14:19 -07002239def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002240 print(docstring.rstrip("\n"))
2241 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002242
2243
2244def ParseOptions(argv,
2245 docstring,
2246 extra_opts="", extra_long_opts=(),
2247 extra_option_handler=None):
2248 """Parse the options in argv and return any arguments that aren't
2249 flags. docstring is the calling module's docstring, to be displayed
2250 for errors and -h. extra_opts and extra_long_opts are for flags
2251 defined by the caller, which are processed by passing them to
2252 extra_option_handler."""
2253
2254 try:
2255 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002256 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002257 ["help", "verbose", "path=", "signapk_path=",
2258 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002259 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002260 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2261 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002262 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2263 "aftl_key_path=", "aftl_manufacturer_key_path=",
2264 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002265 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002266 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002267 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002268 sys.exit(2)
2269
Doug Zongkereef39442009-04-02 12:14:19 -07002270 for o, a in opts:
2271 if o in ("-h", "--help"):
2272 Usage(docstring)
2273 sys.exit()
2274 elif o in ("-v", "--verbose"):
2275 OPTIONS.verbose = True
2276 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002277 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002278 elif o in ("--signapk_path",):
2279 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002280 elif o in ("--signapk_shared_library_path",):
2281 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002282 elif o in ("--extra_signapk_args",):
2283 OPTIONS.extra_signapk_args = shlex.split(a)
2284 elif o in ("--java_path",):
2285 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002286 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002287 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002288 elif o in ("--android_jar_path",):
2289 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002290 elif o in ("--public_key_suffix",):
2291 OPTIONS.public_key_suffix = a
2292 elif o in ("--private_key_suffix",):
2293 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002294 elif o in ("--boot_signer_path",):
2295 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002296 elif o in ("--boot_signer_args",):
2297 OPTIONS.boot_signer_args = shlex.split(a)
2298 elif o in ("--verity_signer_path",):
2299 OPTIONS.verity_signer_path = a
2300 elif o in ("--verity_signer_args",):
2301 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002302 elif o in ("--aftl_tool_path",):
2303 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002304 elif o in ("--aftl_server",):
2305 OPTIONS.aftl_server = a
2306 elif o in ("--aftl_key_path",):
2307 OPTIONS.aftl_key_path = a
2308 elif o in ("--aftl_manufacturer_key_path",):
2309 OPTIONS.aftl_manufacturer_key_path = a
2310 elif o in ("--aftl_signer_helper",):
2311 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002312 elif o in ("-s", "--device_specific"):
2313 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002314 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002315 key, value = a.split("=", 1)
2316 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002317 elif o in ("--logfile",):
2318 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002319 else:
2320 if extra_option_handler is None or not extra_option_handler(o, a):
2321 assert False, "unknown option \"%s\"" % (o,)
2322
Doug Zongker85448772014-09-09 14:59:20 -07002323 if OPTIONS.search_path:
2324 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2325 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002326
2327 return args
2328
2329
Tao Bao4c851b12016-09-19 13:54:38 -07002330def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002331 """Make a temp file and add it to the list of things to be deleted
2332 when Cleanup() is called. Return the filename."""
2333 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2334 os.close(fd)
2335 OPTIONS.tempfiles.append(fn)
2336 return fn
2337
2338
Tao Bao1c830bf2017-12-25 10:43:47 -08002339def MakeTempDir(prefix='tmp', suffix=''):
2340 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2341
2342 Returns:
2343 The absolute pathname of the new directory.
2344 """
2345 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2346 OPTIONS.tempfiles.append(dir_name)
2347 return dir_name
2348
2349
Doug Zongkereef39442009-04-02 12:14:19 -07002350def Cleanup():
2351 for i in OPTIONS.tempfiles:
2352 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002353 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002354 else:
2355 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002356 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002357
2358
2359class PasswordManager(object):
2360 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002361 self.editor = os.getenv("EDITOR")
2362 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002363
2364 def GetPasswords(self, items):
2365 """Get passwords corresponding to each string in 'items',
2366 returning a dict. (The dict may have keys in addition to the
2367 values in 'items'.)
2368
2369 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2370 user edit that file to add more needed passwords. If no editor is
2371 available, or $ANDROID_PW_FILE isn't define, prompts the user
2372 interactively in the ordinary way.
2373 """
2374
2375 current = self.ReadFile()
2376
2377 first = True
2378 while True:
2379 missing = []
2380 for i in items:
2381 if i not in current or not current[i]:
2382 missing.append(i)
2383 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002384 if not missing:
2385 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002386
2387 for i in missing:
2388 current[i] = ""
2389
2390 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002391 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002392 if sys.version_info[0] >= 3:
2393 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002394 answer = raw_input("try to edit again? [y]> ").strip()
2395 if answer and answer[0] not in 'yY':
2396 raise RuntimeError("key passwords unavailable")
2397 first = False
2398
2399 current = self.UpdateAndReadFile(current)
2400
Kelvin Zhang0876c412020-06-23 15:06:58 -04002401 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002402 """Prompt the user to enter a value (password) for each key in
2403 'current' whose value is fales. Returns a new dict with all the
2404 values.
2405 """
2406 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002407 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002408 if v:
2409 result[k] = v
2410 else:
2411 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002412 result[k] = getpass.getpass(
2413 "Enter password for %s key> " % k).strip()
2414 if result[k]:
2415 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002416 return result
2417
2418 def UpdateAndReadFile(self, current):
2419 if not self.editor or not self.pwfile:
2420 return self.PromptResult(current)
2421
2422 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002423 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002424 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2425 f.write("# (Additional spaces are harmless.)\n\n")
2426
2427 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002428 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002429 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002430 f.write("[[[ %s ]]] %s\n" % (v, k))
2431 if not v and first_line is None:
2432 # position cursor on first line with no password.
2433 first_line = i + 4
2434 f.close()
2435
Tao Bao986ee862018-10-04 15:46:16 -07002436 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002437
2438 return self.ReadFile()
2439
2440 def ReadFile(self):
2441 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002442 if self.pwfile is None:
2443 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002444 try:
2445 f = open(self.pwfile, "r")
2446 for line in f:
2447 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002448 if not line or line[0] == '#':
2449 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002450 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2451 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002452 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002453 else:
2454 result[m.group(2)] = m.group(1)
2455 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002456 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002457 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002458 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002459 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002460
2461
Dan Albert8e0178d2015-01-27 15:53:15 -08002462def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2463 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002464
2465 # http://b/18015246
2466 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2467 # for files larger than 2GiB. We can work around this by adjusting their
2468 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2469 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2470 # it isn't clear to me exactly what circumstances cause this).
2471 # `zipfile.write()` must be used directly to work around this.
2472 #
2473 # This mess can be avoided if we port to python3.
2474 saved_zip64_limit = zipfile.ZIP64_LIMIT
2475 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2476
2477 if compress_type is None:
2478 compress_type = zip_file.compression
2479 if arcname is None:
2480 arcname = filename
2481
2482 saved_stat = os.stat(filename)
2483
2484 try:
2485 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2486 # file to be zipped and reset it when we're done.
2487 os.chmod(filename, perms)
2488
2489 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002490 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2491 # intentional. zip stores datetimes in local time without a time zone
2492 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2493 # in the zip archive.
2494 local_epoch = datetime.datetime.fromtimestamp(0)
2495 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002496 os.utime(filename, (timestamp, timestamp))
2497
2498 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2499 finally:
2500 os.chmod(filename, saved_stat.st_mode)
2501 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2502 zipfile.ZIP64_LIMIT = saved_zip64_limit
2503
2504
Tao Bao58c1b962015-05-20 09:32:18 -07002505def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002506 compress_type=None):
2507 """Wrap zipfile.writestr() function to work around the zip64 limit.
2508
2509 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2510 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2511 when calling crc32(bytes).
2512
2513 But it still works fine to write a shorter string into a large zip file.
2514 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2515 when we know the string won't be too long.
2516 """
2517
2518 saved_zip64_limit = zipfile.ZIP64_LIMIT
2519 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2520
2521 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2522 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002523 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002524 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002525 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002526 else:
Tao Baof3282b42015-04-01 11:21:55 -07002527 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002528 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2529 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2530 # such a case (since
2531 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2532 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2533 # permission bits. We follow the logic in Python 3 to get consistent
2534 # behavior between using the two versions.
2535 if not zinfo.external_attr:
2536 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002537
2538 # If compress_type is given, it overrides the value in zinfo.
2539 if compress_type is not None:
2540 zinfo.compress_type = compress_type
2541
Tao Bao58c1b962015-05-20 09:32:18 -07002542 # If perms is given, it has a priority.
2543 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002544 # If perms doesn't set the file type, mark it as a regular file.
2545 if perms & 0o770000 == 0:
2546 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002547 zinfo.external_attr = perms << 16
2548
Tao Baof3282b42015-04-01 11:21:55 -07002549 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002550 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2551
Dan Albert8b72aef2015-03-23 19:13:21 -07002552 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002553 zipfile.ZIP64_LIMIT = saved_zip64_limit
2554
2555
Tao Bao89d7ab22017-12-14 17:05:33 -08002556def ZipDelete(zip_filename, entries):
2557 """Deletes entries from a ZIP file.
2558
2559 Since deleting entries from a ZIP file is not supported, it shells out to
2560 'zip -d'.
2561
2562 Args:
2563 zip_filename: The name of the ZIP file.
2564 entries: The name of the entry, or the list of names to be deleted.
2565
2566 Raises:
2567 AssertionError: In case of non-zero return from 'zip'.
2568 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002569 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002570 entries = [entries]
2571 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002572 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002573
2574
Tao Baof3282b42015-04-01 11:21:55 -07002575def ZipClose(zip_file):
2576 # http://b/18015246
2577 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2578 # central directory.
2579 saved_zip64_limit = zipfile.ZIP64_LIMIT
2580 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2581
2582 zip_file.close()
2583
2584 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002585
2586
2587class DeviceSpecificParams(object):
2588 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002589
Doug Zongker05d3dea2009-06-22 11:32:31 -07002590 def __init__(self, **kwargs):
2591 """Keyword arguments to the constructor become attributes of this
2592 object, which is passed to all functions in the device-specific
2593 module."""
Tao Bao38884282019-07-10 22:20:56 -07002594 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002595 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002596 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002597
2598 if self.module is None:
2599 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002600 if not path:
2601 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002602 try:
2603 if os.path.isdir(path):
2604 info = imp.find_module("releasetools", [path])
2605 else:
2606 d, f = os.path.split(path)
2607 b, x = os.path.splitext(f)
2608 if x == ".py":
2609 f = b
2610 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002611 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002612 self.module = imp.load_module("device_specific", *info)
2613 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002614 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002615
2616 def _DoCall(self, function_name, *args, **kwargs):
2617 """Call the named function in the device-specific module, passing
2618 the given args and kwargs. The first argument to the call will be
2619 the DeviceSpecific object itself. If there is no module, or the
2620 module does not define the function, return the value of the
2621 'default' kwarg (which itself defaults to None)."""
2622 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002623 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002624 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2625
2626 def FullOTA_Assertions(self):
2627 """Called after emitting the block of assertions at the top of a
2628 full OTA package. Implementations can add whatever additional
2629 assertions they like."""
2630 return self._DoCall("FullOTA_Assertions")
2631
Doug Zongkere5ff5902012-01-17 10:55:37 -08002632 def FullOTA_InstallBegin(self):
2633 """Called at the start of full OTA installation."""
2634 return self._DoCall("FullOTA_InstallBegin")
2635
Yifan Hong10c530d2018-12-27 17:34:18 -08002636 def FullOTA_GetBlockDifferences(self):
2637 """Called during full OTA installation and verification.
2638 Implementation should return a list of BlockDifference objects describing
2639 the update on each additional partitions.
2640 """
2641 return self._DoCall("FullOTA_GetBlockDifferences")
2642
Doug Zongker05d3dea2009-06-22 11:32:31 -07002643 def FullOTA_InstallEnd(self):
2644 """Called at the end of full OTA installation; typically this is
2645 used to install the image for the device's baseband processor."""
2646 return self._DoCall("FullOTA_InstallEnd")
2647
2648 def IncrementalOTA_Assertions(self):
2649 """Called after emitting the block of assertions at the top of an
2650 incremental OTA package. Implementations can add whatever
2651 additional assertions they like."""
2652 return self._DoCall("IncrementalOTA_Assertions")
2653
Doug Zongkere5ff5902012-01-17 10:55:37 -08002654 def IncrementalOTA_VerifyBegin(self):
2655 """Called at the start of the verification phase of incremental
2656 OTA installation; additional checks can be placed here to abort
2657 the script before any changes are made."""
2658 return self._DoCall("IncrementalOTA_VerifyBegin")
2659
Doug Zongker05d3dea2009-06-22 11:32:31 -07002660 def IncrementalOTA_VerifyEnd(self):
2661 """Called at the end of the verification phase of incremental OTA
2662 installation; additional checks can be placed here to abort the
2663 script before any changes are made."""
2664 return self._DoCall("IncrementalOTA_VerifyEnd")
2665
Doug Zongkere5ff5902012-01-17 10:55:37 -08002666 def IncrementalOTA_InstallBegin(self):
2667 """Called at the start of incremental OTA installation (after
2668 verification is complete)."""
2669 return self._DoCall("IncrementalOTA_InstallBegin")
2670
Yifan Hong10c530d2018-12-27 17:34:18 -08002671 def IncrementalOTA_GetBlockDifferences(self):
2672 """Called during incremental OTA installation and verification.
2673 Implementation should return a list of BlockDifference objects describing
2674 the update on each additional partitions.
2675 """
2676 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2677
Doug Zongker05d3dea2009-06-22 11:32:31 -07002678 def IncrementalOTA_InstallEnd(self):
2679 """Called at the end of incremental OTA installation; typically
2680 this is used to install the image for the device's baseband
2681 processor."""
2682 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002683
Tao Bao9bc6bb22015-11-09 16:58:28 -08002684 def VerifyOTA_Assertions(self):
2685 return self._DoCall("VerifyOTA_Assertions")
2686
Tao Bao76def242017-11-21 09:25:31 -08002687
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002688class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002689 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002690 self.name = name
2691 self.data = data
2692 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002693 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002694 self.sha1 = sha1(data).hexdigest()
2695
2696 @classmethod
2697 def FromLocalFile(cls, name, diskname):
2698 f = open(diskname, "rb")
2699 data = f.read()
2700 f.close()
2701 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002702
2703 def WriteToTemp(self):
2704 t = tempfile.NamedTemporaryFile()
2705 t.write(self.data)
2706 t.flush()
2707 return t
2708
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002709 def WriteToDir(self, d):
2710 with open(os.path.join(d, self.name), "wb") as fp:
2711 fp.write(self.data)
2712
Geremy Condra36bd3652014-02-06 19:45:10 -08002713 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002714 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002715
Tao Bao76def242017-11-21 09:25:31 -08002716
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002717DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002718 ".gz": "imgdiff",
2719 ".zip": ["imgdiff", "-z"],
2720 ".jar": ["imgdiff", "-z"],
2721 ".apk": ["imgdiff", "-z"],
2722 ".img": "imgdiff",
2723}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002724
Tao Bao76def242017-11-21 09:25:31 -08002725
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002726class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002727 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002728 self.tf = tf
2729 self.sf = sf
2730 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002731 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002732
2733 def ComputePatch(self):
2734 """Compute the patch (as a string of data) needed to turn sf into
2735 tf. Returns the same tuple as GetPatch()."""
2736
2737 tf = self.tf
2738 sf = self.sf
2739
Doug Zongker24cd2802012-08-14 16:36:15 -07002740 if self.diff_program:
2741 diff_program = self.diff_program
2742 else:
2743 ext = os.path.splitext(tf.name)[1]
2744 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002745
2746 ttemp = tf.WriteToTemp()
2747 stemp = sf.WriteToTemp()
2748
2749 ext = os.path.splitext(tf.name)[1]
2750
2751 try:
2752 ptemp = tempfile.NamedTemporaryFile()
2753 if isinstance(diff_program, list):
2754 cmd = copy.copy(diff_program)
2755 else:
2756 cmd = [diff_program]
2757 cmd.append(stemp.name)
2758 cmd.append(ttemp.name)
2759 cmd.append(ptemp.name)
2760 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002761 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002762
Doug Zongkerf8340082014-08-05 10:39:37 -07002763 def run():
2764 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002765 if e:
2766 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002767 th = threading.Thread(target=run)
2768 th.start()
2769 th.join(timeout=300) # 5 mins
2770 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002771 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002772 p.terminate()
2773 th.join(5)
2774 if th.is_alive():
2775 p.kill()
2776 th.join()
2777
Tianjie Xua2a9f992018-01-05 15:15:54 -08002778 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002779 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002780 self.patch = None
2781 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002782 diff = ptemp.read()
2783 finally:
2784 ptemp.close()
2785 stemp.close()
2786 ttemp.close()
2787
2788 self.patch = diff
2789 return self.tf, self.sf, self.patch
2790
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002791 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002792 """Returns a tuple of (target_file, source_file, patch_data).
2793
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002794 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002795 computing the patch failed.
2796 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002797 return self.tf, self.sf, self.patch
2798
2799
2800def ComputeDifferences(diffs):
2801 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002802 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002803
2804 # Do the largest files first, to try and reduce the long-pole effect.
2805 by_size = [(i.tf.size, i) for i in diffs]
2806 by_size.sort(reverse=True)
2807 by_size = [i[1] for i in by_size]
2808
2809 lock = threading.Lock()
2810 diff_iter = iter(by_size) # accessed under lock
2811
2812 def worker():
2813 try:
2814 lock.acquire()
2815 for d in diff_iter:
2816 lock.release()
2817 start = time.time()
2818 d.ComputePatch()
2819 dur = time.time() - start
2820 lock.acquire()
2821
2822 tf, sf, patch = d.GetPatch()
2823 if sf.name == tf.name:
2824 name = tf.name
2825 else:
2826 name = "%s (%s)" % (tf.name, sf.name)
2827 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002828 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002829 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002830 logger.info(
2831 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2832 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002833 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002834 except Exception:
2835 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002836 raise
2837
2838 # start worker threads; wait for them all to finish.
2839 threads = [threading.Thread(target=worker)
2840 for i in range(OPTIONS.worker_threads)]
2841 for th in threads:
2842 th.start()
2843 while threads:
2844 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002845
2846
Dan Albert8b72aef2015-03-23 19:13:21 -07002847class BlockDifference(object):
2848 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002849 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002850 self.tgt = tgt
2851 self.src = src
2852 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002853 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002854 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002855
Tao Baodd2a5892015-03-12 12:32:37 -07002856 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002857 version = max(
2858 int(i) for i in
2859 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002860 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002861 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002862
Tianjie Xu41976c72019-07-03 13:57:01 -07002863 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2864 version=self.version,
2865 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002866 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002867 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002868 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002869 self.touched_src_ranges = b.touched_src_ranges
2870 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002871
Yifan Hong10c530d2018-12-27 17:34:18 -08002872 # On devices with dynamic partitions, for new partitions,
2873 # src is None but OPTIONS.source_info_dict is not.
2874 if OPTIONS.source_info_dict is None:
2875 is_dynamic_build = OPTIONS.info_dict.get(
2876 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002877 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002878 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002879 is_dynamic_build = OPTIONS.source_info_dict.get(
2880 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002881 is_dynamic_source = partition in shlex.split(
2882 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002883
Yifan Hongbb2658d2019-01-25 12:30:58 -08002884 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002885 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2886
Yifan Hongbb2658d2019-01-25 12:30:58 -08002887 # For dynamic partitions builds, check partition list in both source
2888 # and target build because new partitions may be added, and existing
2889 # partitions may be removed.
2890 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2891
Yifan Hong10c530d2018-12-27 17:34:18 -08002892 if is_dynamic:
2893 self.device = 'map_partition("%s")' % partition
2894 else:
2895 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002896 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2897 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002898 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002899 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2900 OPTIONS.source_info_dict)
2901 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002902
Tao Baod8d14be2016-02-04 14:26:02 -08002903 @property
2904 def required_cache(self):
2905 return self._required_cache
2906
Tao Bao76def242017-11-21 09:25:31 -08002907 def WriteScript(self, script, output_zip, progress=None,
2908 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002909 if not self.src:
2910 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002911 script.Print("Patching %s image unconditionally..." % (self.partition,))
2912 else:
2913 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002914
Dan Albert8b72aef2015-03-23 19:13:21 -07002915 if progress:
2916 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002917 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002918
2919 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002920 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002921
Tao Bao9bc6bb22015-11-09 16:58:28 -08002922 def WriteStrictVerifyScript(self, script):
2923 """Verify all the blocks in the care_map, including clobbered blocks.
2924
2925 This differs from the WriteVerifyScript() function: a) it prints different
2926 error messages; b) it doesn't allow half-way updated images to pass the
2927 verification."""
2928
2929 partition = self.partition
2930 script.Print("Verifying %s..." % (partition,))
2931 ranges = self.tgt.care_map
2932 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002933 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002934 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2935 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002936 self.device, ranges_str,
2937 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002938 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002939 script.AppendExtra("")
2940
Tao Baod522bdc2016-04-12 15:53:16 -07002941 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002942 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002943
2944 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002945 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002946 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002947
2948 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002949 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002950 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002951 ranges = self.touched_src_ranges
2952 expected_sha1 = self.touched_src_sha1
2953 else:
2954 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2955 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002956
2957 # No blocks to be checked, skipping.
2958 if not ranges:
2959 return
2960
Tao Bao5ece99d2015-05-12 11:42:31 -07002961 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002962 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002963 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002964 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2965 '"%s.patch.dat")) then' % (
2966 self.device, ranges_str, expected_sha1,
2967 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002968 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002969 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002970
Tianjie Xufc3422a2015-12-15 11:53:59 -08002971 if self.version >= 4:
2972
2973 # Bug: 21124327
2974 # When generating incrementals for the system and vendor partitions in
2975 # version 4 or newer, explicitly check the first block (which contains
2976 # the superblock) of the partition to see if it's what we expect. If
2977 # this check fails, give an explicit log message about the partition
2978 # having been remounted R/W (the most likely explanation).
2979 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002980 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002981
2982 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002983 if partition == "system":
2984 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2985 else:
2986 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002987 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002988 'ifelse (block_image_recover({device}, "{ranges}") && '
2989 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002990 'package_extract_file("{partition}.transfer.list"), '
2991 '"{partition}.new.dat", "{partition}.patch.dat"), '
2992 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002993 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002994 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002995 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002996
Tao Baodd2a5892015-03-12 12:32:37 -07002997 # Abort the OTA update. Note that the incremental OTA cannot be applied
2998 # even if it may match the checksum of the target partition.
2999 # a) If version < 3, operations like move and erase will make changes
3000 # unconditionally and damage the partition.
3001 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003002 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003003 if partition == "system":
3004 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3005 else:
3006 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3007 script.AppendExtra((
3008 'abort("E%d: %s partition has unexpected contents");\n'
3009 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003010
Yifan Hong10c530d2018-12-27 17:34:18 -08003011 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003012 partition = self.partition
3013 script.Print('Verifying the updated %s image...' % (partition,))
3014 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3015 ranges = self.tgt.care_map
3016 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003017 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003018 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003019 self.device, ranges_str,
3020 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003021
3022 # Bug: 20881595
3023 # Verify that extended blocks are really zeroed out.
3024 if self.tgt.extended:
3025 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003026 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003027 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003028 self.device, ranges_str,
3029 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003030 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003031 if partition == "system":
3032 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3033 else:
3034 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003035 script.AppendExtra(
3036 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003037 ' abort("E%d: %s partition has unexpected non-zero contents after '
3038 'OTA update");\n'
3039 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003040 else:
3041 script.Print('Verified the updated %s image.' % (partition,))
3042
Tianjie Xu209db462016-05-24 17:34:52 -07003043 if partition == "system":
3044 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3045 else:
3046 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3047
Tao Bao5fcaaef2015-06-01 13:40:49 -07003048 script.AppendExtra(
3049 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003050 ' abort("E%d: %s partition has unexpected contents after OTA '
3051 'update");\n'
3052 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003053
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003054 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003055 ZipWrite(output_zip,
3056 '{}.transfer.list'.format(self.path),
3057 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003058
Tao Bao76def242017-11-21 09:25:31 -08003059 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3060 # its size. Quailty 9 almost triples the compression time but doesn't
3061 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003062 # zip | brotli(quality 6) | brotli(quality 9)
3063 # compressed_size: 942M | 869M (~8% reduced) | 854M
3064 # compression_time: 75s | 265s | 719s
3065 # decompression_time: 15s | 25s | 25s
3066
3067 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003068 brotli_cmd = ['brotli', '--quality=6',
3069 '--output={}.new.dat.br'.format(self.path),
3070 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003071 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003072 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003073
3074 new_data_name = '{}.new.dat.br'.format(self.partition)
3075 ZipWrite(output_zip,
3076 '{}.new.dat.br'.format(self.path),
3077 new_data_name,
3078 compress_type=zipfile.ZIP_STORED)
3079 else:
3080 new_data_name = '{}.new.dat'.format(self.partition)
3081 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3082
Dan Albert8e0178d2015-01-27 15:53:15 -08003083 ZipWrite(output_zip,
3084 '{}.patch.dat'.format(self.path),
3085 '{}.patch.dat'.format(self.partition),
3086 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003087
Tianjie Xu209db462016-05-24 17:34:52 -07003088 if self.partition == "system":
3089 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3090 else:
3091 code = ErrorCode.VENDOR_UPDATE_FAILURE
3092
Yifan Hong10c530d2018-12-27 17:34:18 -08003093 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003094 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003095 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003096 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003097 device=self.device, partition=self.partition,
3098 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003099 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003100
Kelvin Zhang0876c412020-06-23 15:06:58 -04003101 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003102 data = source.ReadRangeSet(ranges)
3103 ctx = sha1()
3104
3105 for p in data:
3106 ctx.update(p)
3107
3108 return ctx.hexdigest()
3109
Kelvin Zhang0876c412020-06-23 15:06:58 -04003110 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003111 """Return the hash value for all zero blocks."""
3112 zero_block = '\x00' * 4096
3113 ctx = sha1()
3114 for _ in range(num_blocks):
3115 ctx.update(zero_block)
3116
3117 return ctx.hexdigest()
3118
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003119
Tianjie Xu41976c72019-07-03 13:57:01 -07003120# Expose these two classes to support vendor-specific scripts
3121DataImage = images.DataImage
3122EmptyImage = images.EmptyImage
3123
Tao Bao76def242017-11-21 09:25:31 -08003124
Doug Zongker96a57e72010-09-26 14:57:41 -07003125# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003126PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003127 "ext4": "EMMC",
3128 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003129 "f2fs": "EMMC",
3130 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003131}
Doug Zongker96a57e72010-09-26 14:57:41 -07003132
Kelvin Zhang0876c412020-06-23 15:06:58 -04003133
Yifan Hongbdb32012020-05-07 12:38:53 -07003134def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3135 """
3136 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3137 backwards compatibility. It aborts if the fstab entry has slotselect option
3138 (unless check_no_slot is explicitly set to False).
3139 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003140 fstab = info["fstab"]
3141 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003142 if check_no_slot:
3143 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003144 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003145 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3146 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003147 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003148
3149
Yifan Hongbdb32012020-05-07 12:38:53 -07003150def GetTypeAndDeviceExpr(mount_point, info):
3151 """
3152 Return the filesystem of the partition, and an edify expression that evaluates
3153 to the device at runtime.
3154 """
3155 fstab = info["fstab"]
3156 if fstab:
3157 p = fstab[mount_point]
3158 device_expr = '"%s"' % fstab[mount_point].device
3159 if p.slotselect:
3160 device_expr = 'add_slot_suffix(%s)' % device_expr
3161 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003162 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003163
3164
3165def GetEntryForDevice(fstab, device):
3166 """
3167 Returns:
3168 The first entry in fstab whose device is the given value.
3169 """
3170 if not fstab:
3171 return None
3172 for mount_point in fstab:
3173 if fstab[mount_point].device == device:
3174 return fstab[mount_point]
3175 return None
3176
Kelvin Zhang0876c412020-06-23 15:06:58 -04003177
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003178def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003179 """Parses and converts a PEM-encoded certificate into DER-encoded.
3180
3181 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3182
3183 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003184 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003185 """
3186 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003187 save = False
3188 for line in data.split("\n"):
3189 if "--END CERTIFICATE--" in line:
3190 break
3191 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003192 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003193 if "--BEGIN CERTIFICATE--" in line:
3194 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003195 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003196 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003197
Tao Bao04e1f012018-02-04 12:13:35 -08003198
3199def ExtractPublicKey(cert):
3200 """Extracts the public key (PEM-encoded) from the given certificate file.
3201
3202 Args:
3203 cert: The certificate filename.
3204
3205 Returns:
3206 The public key string.
3207
3208 Raises:
3209 AssertionError: On non-zero return from 'openssl'.
3210 """
3211 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3212 # While openssl 1.1 writes the key into the given filename followed by '-out',
3213 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3214 # stdout instead.
3215 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3216 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3217 pubkey, stderrdata = proc.communicate()
3218 assert proc.returncode == 0, \
3219 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3220 return pubkey
3221
3222
Tao Bao1ac886e2019-06-26 11:58:22 -07003223def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003224 """Extracts the AVB public key from the given public or private key.
3225
3226 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003227 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003228 key: The input key file, which should be PEM-encoded public or private key.
3229
3230 Returns:
3231 The path to the extracted AVB public key file.
3232 """
3233 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3234 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003235 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003236 return output
3237
3238
Doug Zongker412c02f2014-02-13 10:58:24 -08003239def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3240 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003241 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003242
Tao Bao6d5d6232018-03-09 17:04:42 -08003243 Most of the space in the boot and recovery images is just the kernel, which is
3244 identical for the two, so the resulting patch should be efficient. Add it to
3245 the output zip, along with a shell script that is run from init.rc on first
3246 boot to actually do the patching and install the new recovery image.
3247
3248 Args:
3249 input_dir: The top-level input directory of the target-files.zip.
3250 output_sink: The callback function that writes the result.
3251 recovery_img: File object for the recovery image.
3252 boot_img: File objects for the boot image.
3253 info_dict: A dict returned by common.LoadInfoDict() on the input
3254 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003255 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003256 if info_dict is None:
3257 info_dict = OPTIONS.info_dict
3258
Tao Bao6d5d6232018-03-09 17:04:42 -08003259 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003260 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3261
3262 if board_uses_vendorimage:
3263 # In this case, the output sink is rooted at VENDOR
3264 recovery_img_path = "etc/recovery.img"
3265 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3266 sh_dir = "bin"
3267 else:
3268 # In this case the output sink is rooted at SYSTEM
3269 recovery_img_path = "vendor/etc/recovery.img"
3270 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3271 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003272
Tao Baof2cffbd2015-07-22 12:33:18 -07003273 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003274 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003275
3276 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003277 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003278 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003279 # With system-root-image, boot and recovery images will have mismatching
3280 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3281 # to handle such a case.
3282 if system_root_image:
3283 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003284 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003285 assert not os.path.exists(path)
3286 else:
3287 diff_program = ["imgdiff"]
3288 if os.path.exists(path):
3289 diff_program.append("-b")
3290 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003291 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003292 else:
3293 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003294
3295 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3296 _, _, patch = d.ComputePatch()
3297 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003298
Dan Albertebb19aa2015-03-27 19:11:53 -07003299 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003300 # The following GetTypeAndDevice()s need to use the path in the target
3301 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003302 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3303 check_no_slot=False)
3304 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3305 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003306 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003307 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003308
Tao Baof2cffbd2015-07-22 12:33:18 -07003309 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003310
3311 # Note that we use /vendor to refer to the recovery resources. This will
3312 # work for a separate vendor partition mounted at /vendor or a
3313 # /system/vendor subdirectory on the system partition, for which init will
3314 # create a symlink from /vendor to /system/vendor.
3315
3316 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003317if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3318 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003319 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003320 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3321 log -t recovery "Installing new recovery image: succeeded" || \\
3322 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003323else
3324 log -t recovery "Recovery image already installed"
3325fi
3326""" % {'type': recovery_type,
3327 'device': recovery_device,
3328 'sha1': recovery_img.sha1,
3329 'size': recovery_img.size}
3330 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003331 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003332if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3333 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003334 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003335 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3336 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3337 log -t recovery "Installing new recovery image: succeeded" || \\
3338 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003339else
3340 log -t recovery "Recovery image already installed"
3341fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003342""" % {'boot_size': boot_img.size,
3343 'boot_sha1': boot_img.sha1,
3344 'recovery_size': recovery_img.size,
3345 'recovery_sha1': recovery_img.sha1,
3346 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003347 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003348 'recovery_type': recovery_type,
3349 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003350 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003351
Bill Peckhame868aec2019-09-17 17:06:47 -07003352 # The install script location moved from /system/etc to /system/bin in the L
3353 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3354 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003355
Tao Bao32fcdab2018-10-12 10:30:39 -07003356 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003357
Tao Baoda30cfa2017-12-01 16:19:46 -08003358 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003359
3360
3361class DynamicPartitionUpdate(object):
3362 def __init__(self, src_group=None, tgt_group=None, progress=None,
3363 block_difference=None):
3364 self.src_group = src_group
3365 self.tgt_group = tgt_group
3366 self.progress = progress
3367 self.block_difference = block_difference
3368
3369 @property
3370 def src_size(self):
3371 if not self.block_difference:
3372 return 0
3373 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3374
3375 @property
3376 def tgt_size(self):
3377 if not self.block_difference:
3378 return 0
3379 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3380
3381 @staticmethod
3382 def _GetSparseImageSize(img):
3383 if not img:
3384 return 0
3385 return img.blocksize * img.total_blocks
3386
3387
3388class DynamicGroupUpdate(object):
3389 def __init__(self, src_size=None, tgt_size=None):
3390 # None: group does not exist. 0: no size limits.
3391 self.src_size = src_size
3392 self.tgt_size = tgt_size
3393
3394
3395class DynamicPartitionsDifference(object):
3396 def __init__(self, info_dict, block_diffs, progress_dict=None,
3397 source_info_dict=None):
3398 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003399 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003400
3401 self._remove_all_before_apply = False
3402 if source_info_dict is None:
3403 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003404 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003405
Tao Baof1113e92019-06-18 12:10:14 -07003406 block_diff_dict = collections.OrderedDict(
3407 [(e.partition, e) for e in block_diffs])
3408
Yifan Hong10c530d2018-12-27 17:34:18 -08003409 assert len(block_diff_dict) == len(block_diffs), \
3410 "Duplicated BlockDifference object for {}".format(
3411 [partition for partition, count in
3412 collections.Counter(e.partition for e in block_diffs).items()
3413 if count > 1])
3414
Yifan Hong79997e52019-01-23 16:56:19 -08003415 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003416
3417 for p, block_diff in block_diff_dict.items():
3418 self._partition_updates[p] = DynamicPartitionUpdate()
3419 self._partition_updates[p].block_difference = block_diff
3420
3421 for p, progress in progress_dict.items():
3422 if p in self._partition_updates:
3423 self._partition_updates[p].progress = progress
3424
3425 tgt_groups = shlex.split(info_dict.get(
3426 "super_partition_groups", "").strip())
3427 src_groups = shlex.split(source_info_dict.get(
3428 "super_partition_groups", "").strip())
3429
3430 for g in tgt_groups:
3431 for p in shlex.split(info_dict.get(
3432 "super_%s_partition_list" % g, "").strip()):
3433 assert p in self._partition_updates, \
3434 "{} is in target super_{}_partition_list but no BlockDifference " \
3435 "object is provided.".format(p, g)
3436 self._partition_updates[p].tgt_group = g
3437
3438 for g in src_groups:
3439 for p in shlex.split(source_info_dict.get(
3440 "super_%s_partition_list" % g, "").strip()):
3441 assert p in self._partition_updates, \
3442 "{} is in source super_{}_partition_list but no BlockDifference " \
3443 "object is provided.".format(p, g)
3444 self._partition_updates[p].src_group = g
3445
Yifan Hong45433e42019-01-18 13:55:25 -08003446 target_dynamic_partitions = set(shlex.split(info_dict.get(
3447 "dynamic_partition_list", "").strip()))
3448 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3449 if u.tgt_size)
3450 assert block_diffs_with_target == target_dynamic_partitions, \
3451 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3452 list(target_dynamic_partitions), list(block_diffs_with_target))
3453
3454 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3455 "dynamic_partition_list", "").strip()))
3456 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3457 if u.src_size)
3458 assert block_diffs_with_source == source_dynamic_partitions, \
3459 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3460 list(source_dynamic_partitions), list(block_diffs_with_source))
3461
Yifan Hong10c530d2018-12-27 17:34:18 -08003462 if self._partition_updates:
3463 logger.info("Updating dynamic partitions %s",
3464 self._partition_updates.keys())
3465
Yifan Hong79997e52019-01-23 16:56:19 -08003466 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003467
3468 for g in tgt_groups:
3469 self._group_updates[g] = DynamicGroupUpdate()
3470 self._group_updates[g].tgt_size = int(info_dict.get(
3471 "super_%s_group_size" % g, "0").strip())
3472
3473 for g in src_groups:
3474 if g not in self._group_updates:
3475 self._group_updates[g] = DynamicGroupUpdate()
3476 self._group_updates[g].src_size = int(source_info_dict.get(
3477 "super_%s_group_size" % g, "0").strip())
3478
3479 self._Compute()
3480
3481 def WriteScript(self, script, output_zip, write_verify_script=False):
3482 script.Comment('--- Start patching dynamic partitions ---')
3483 for p, u in self._partition_updates.items():
3484 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3485 script.Comment('Patch partition %s' % p)
3486 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3487 write_verify_script=False)
3488
3489 op_list_path = MakeTempFile()
3490 with open(op_list_path, 'w') as f:
3491 for line in self._op_list:
3492 f.write('{}\n'.format(line))
3493
3494 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3495
3496 script.Comment('Update dynamic partition metadata')
3497 script.AppendExtra('assert(update_dynamic_partitions('
3498 'package_extract_file("dynamic_partitions_op_list")));')
3499
3500 if write_verify_script:
3501 for p, u in self._partition_updates.items():
3502 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3503 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003504 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003505
3506 for p, u in self._partition_updates.items():
3507 if u.tgt_size and u.src_size <= u.tgt_size:
3508 script.Comment('Patch partition %s' % p)
3509 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3510 write_verify_script=write_verify_script)
3511 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003512 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003513
3514 script.Comment('--- End patching dynamic partitions ---')
3515
3516 def _Compute(self):
3517 self._op_list = list()
3518
3519 def append(line):
3520 self._op_list.append(line)
3521
3522 def comment(line):
3523 self._op_list.append("# %s" % line)
3524
3525 if self._remove_all_before_apply:
3526 comment('Remove all existing dynamic partitions and groups before '
3527 'applying full OTA')
3528 append('remove_all_groups')
3529
3530 for p, u in self._partition_updates.items():
3531 if u.src_group and not u.tgt_group:
3532 append('remove %s' % p)
3533
3534 for p, u in self._partition_updates.items():
3535 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3536 comment('Move partition %s from %s to default' % (p, u.src_group))
3537 append('move %s default' % p)
3538
3539 for p, u in self._partition_updates.items():
3540 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3541 comment('Shrink partition %s from %d to %d' %
3542 (p, u.src_size, u.tgt_size))
3543 append('resize %s %s' % (p, u.tgt_size))
3544
3545 for g, u in self._group_updates.items():
3546 if u.src_size is not None and u.tgt_size is None:
3547 append('remove_group %s' % g)
3548 if (u.src_size is not None and u.tgt_size is not None and
3549 u.src_size > u.tgt_size):
3550 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3551 append('resize_group %s %d' % (g, u.tgt_size))
3552
3553 for g, u in self._group_updates.items():
3554 if u.src_size is None and u.tgt_size is not None:
3555 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3556 append('add_group %s %d' % (g, u.tgt_size))
3557 if (u.src_size is not None and u.tgt_size is not None and
3558 u.src_size < u.tgt_size):
3559 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3560 append('resize_group %s %d' % (g, u.tgt_size))
3561
3562 for p, u in self._partition_updates.items():
3563 if u.tgt_group and not u.src_group:
3564 comment('Add partition %s to group %s' % (p, u.tgt_group))
3565 append('add %s %s' % (p, u.tgt_group))
3566
3567 for p, u in self._partition_updates.items():
3568 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003569 comment('Grow partition %s from %d to %d' %
3570 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003571 append('resize %s %d' % (p, u.tgt_size))
3572
3573 for p, u in self._partition_updates.items():
3574 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3575 comment('Move partition %s from default to %s' %
3576 (p, u.tgt_group))
3577 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003578
3579
Yifan Hong85ac5012021-01-07 14:43:46 -08003580def GetBootImageBuildProp(boot_img):
Yifan Hongc65a0542021-01-07 14:21:01 -08003581 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003582 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003583
3584 Args:
3585 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3586
3587 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003588 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003589 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003590 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
Yifan Hong85ac5012021-01-07 14:43:46 -08003614 return prop_file
Yifan Hongc65a0542021-01-07 14:21:01 -08003615
Yifan Hong85ac5012021-01-07 14:43:46 -08003616 except ExternalError as e:
3617 logger.warning('Unable to get boot image build props: %s', e)
3618 return None
3619
3620
3621def GetBootImageTimestamp(boot_img):
3622 """
3623 Get timestamp from ramdisk within the boot image
3624
3625 Args:
3626 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3627
3628 Return:
3629 An integer that corresponds to the timestamp of the boot image, or None
3630 if file has unknown format. Raise exception if an unexpected error has
3631 occurred.
3632 """
3633 prop_file = GetBootImageBuildProp(boot_img)
3634 if not prop_file:
3635 return None
3636
3637 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3638 if props is None:
3639 return None
3640
3641 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003642 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3643 if timestamp:
3644 return int(timestamp)
3645 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
3646 return None
3647
3648 except ExternalError as e:
3649 logger.warning('Unable to get boot image timestamp: %s', e)
3650 return None