blob: 6ec1b9468730b5af19b755069e8e4676d608e509 [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
Kelvin Zhang27324132021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -080071 if not os.path.exists(os.path.join(self.search_path, self.signapk_path)):
72 if "ANDROID_HOST_OUT" in os.environ:
73 self.search_path = os.environ["ANDROID_HOST_OUT"]
Alex Klyubin9667b182015-12-10 13:38:50 -080074 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.extra_signapk_args = []
76 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080077 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080078 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070079 self.public_key_suffix = ".x509.pem"
80 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070081 # use otatools built boot_signer by default
82 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070083 self.boot_signer_args = []
84 self.verity_signer_path = None
85 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070086 self.verbose = False
87 self.tempfiles = []
88 self.device_specific = None
89 self.extras = {}
90 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070091 self.source_info_dict = None
92 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070093 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070094 # Stash size cannot exceed cache_size * threshold.
95 self.cache_size = None
96 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070097 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070098 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -070099
100
101OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700102
Tao Bao71197512018-10-11 14:08:45 -0700103# The block size that's used across the releasetools scripts.
104BLOCK_SIZE = 4096
105
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800106# Values for "certificate" in apkcerts that mean special things.
107SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
108
Tao Bao5cc0abb2019-03-21 10:18:05 -0700109# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
110# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800111# descriptor into vbmeta.img. When adding a new entry here, the
112# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
113# accordingly.
Devin Mooreafdd7c72021-12-13 22:04:08 +0000114AVB_PARTITIONS = ('boot', 'init_boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
Andrew Sculle077cf72021-02-18 10:27:29 +0000115 'system', 'system_ext', 'vendor', 'vendor_boot',
116 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800117
Tao Bao08c190f2019-06-03 23:07:58 -0700118# Chained VBMeta partitions.
119AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
120
Tianjie Xu861f4132018-09-12 11:49:33 -0700121# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400122PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700123 'system',
124 'vendor',
125 'product',
126 'system_ext',
127 'odm',
128 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700129 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400130]
Tianjie Xu861f4132018-09-12 11:49:33 -0700131
Yifan Hong5057b952021-01-07 14:09:57 -0800132# Partitions with a build.prop file
Devin Mooreafdd7c72021-12-13 22:04:08 +0000133PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot', 'init_boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800134
Yifan Hongc65a0542021-01-07 14:21:01 -0800135# See sysprop.mk. If file is moved, add new search paths here; don't remove
136# existing search paths.
137RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700138
Kelvin Zhang563750f2021-04-28 12:46:17 -0400139
Tianjie Xu209db462016-05-24 17:34:52 -0700140class ErrorCode(object):
141 """Define error_codes for failures that happen during the actual
142 update package installation.
143
144 Error codes 0-999 are reserved for failures before the package
145 installation (i.e. low battery, package verification failure).
146 Detailed code in 'bootable/recovery/error_code.h' """
147
148 SYSTEM_VERIFICATION_FAILURE = 1000
149 SYSTEM_UPDATE_FAILURE = 1001
150 SYSTEM_UNEXPECTED_CONTENTS = 1002
151 SYSTEM_NONZERO_CONTENTS = 1003
152 SYSTEM_RECOVER_FAILURE = 1004
153 VENDOR_VERIFICATION_FAILURE = 2000
154 VENDOR_UPDATE_FAILURE = 2001
155 VENDOR_UNEXPECTED_CONTENTS = 2002
156 VENDOR_NONZERO_CONTENTS = 2003
157 VENDOR_RECOVER_FAILURE = 2004
158 OEM_PROP_MISMATCH = 3000
159 FINGERPRINT_MISMATCH = 3001
160 THUMBPRINT_MISMATCH = 3002
161 OLDER_BUILD = 3003
162 DEVICE_MISMATCH = 3004
163 BAD_PATCH_FILE = 3005
164 INSUFFICIENT_CACHE_SPACE = 3006
165 TUNE_PARTITION_FAILURE = 3007
166 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800167
Tao Bao80921982018-03-21 21:02:19 -0700168
Dan Albert8b72aef2015-03-23 19:13:21 -0700169class ExternalError(RuntimeError):
170 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700171
172
Tao Bao32fcdab2018-10-12 10:30:39 -0700173def InitLogging():
174 DEFAULT_LOGGING_CONFIG = {
175 'version': 1,
176 'disable_existing_loggers': False,
177 'formatters': {
178 'standard': {
179 'format':
180 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
181 'datefmt': '%Y-%m-%d %H:%M:%S',
182 },
183 },
184 'handlers': {
185 'default': {
186 'class': 'logging.StreamHandler',
187 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700188 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700189 },
190 },
191 'loggers': {
192 '': {
193 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700195 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 }
197 }
198 }
199 env_config = os.getenv('LOGGING_CONFIG')
200 if env_config:
201 with open(env_config) as f:
202 config = json.load(f)
203 else:
204 config = DEFAULT_LOGGING_CONFIG
205
206 # Increase the logging level for verbose mode.
207 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700208 config = copy.deepcopy(config)
209 config['handlers']['default']['level'] = 'INFO'
210
211 if OPTIONS.logfile:
212 config = copy.deepcopy(config)
213 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400214 'class': 'logging.FileHandler',
215 'formatter': 'standard',
216 'level': 'INFO',
217 'mode': 'w',
218 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700219 }
220 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700221
222 logging.config.dictConfig(config)
223
224
Yifan Hong8e332ff2020-07-29 17:51:55 -0700225def SetHostToolLocation(tool_name, location):
226 OPTIONS.host_tools[tool_name] = location
227
Kelvin Zhang563750f2021-04-28 12:46:17 -0400228
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900229def FindHostToolPath(tool_name):
230 """Finds the path to the host tool.
231
232 Args:
233 tool_name: name of the tool to find
234 Returns:
235 path to the tool if found under either one of the host_tools map or under
236 the same directory as this binary is located at. If not found, tool_name
237 is returned.
238 """
239 if tool_name in OPTIONS.host_tools:
240 return OPTIONS.host_tools[tool_name]
241
242 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
243 tool_path = os.path.join(my_dir, tool_name)
244 if os.path.exists(tool_path):
245 return tool_path
246
247 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700248
Kelvin Zhang563750f2021-04-28 12:46:17 -0400249
Tao Bao39451582017-05-04 11:10:47 -0700250def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700251 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700252
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 Args:
254 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700255 verbose: Whether the commands should be shown. Default to the global
256 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700257 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
258 stdin, etc. stdout and stderr will default to subprocess.PIPE and
259 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800260 universal_newlines will default to True, as most of the users in
261 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700262
263 Returns:
264 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700265 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700266 if 'stdout' not in kwargs and 'stderr' not in kwargs:
267 kwargs['stdout'] = subprocess.PIPE
268 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800269 if 'universal_newlines' not in kwargs:
270 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700271
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900272 if args:
273 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700274 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900275 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276
Kelvin Zhang766eea72021-06-03 09:36:08 -0400277 if verbose is None:
278 verbose = OPTIONS.verbose
279
Tao Bao32fcdab2018-10-12 10:30:39 -0700280 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400281 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700282 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700283 return subprocess.Popen(args, **kwargs)
284
285
Tao Bao986ee862018-10-04 15:46:16 -0700286def RunAndCheckOutput(args, verbose=None, **kwargs):
287 """Runs the given command and returns the output.
288
289 Args:
290 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700291 verbose: Whether the commands should be shown. Default to the global
292 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700293 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
294 stdin, etc. stdout and stderr will default to subprocess.PIPE and
295 subprocess.STDOUT respectively unless caller specifies any of them.
296
297 Returns:
298 The output string.
299
300 Raises:
301 ExternalError: On non-zero exit from the command.
302 """
Tao Bao986ee862018-10-04 15:46:16 -0700303 proc = Run(args, verbose=verbose, **kwargs)
304 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800305 if output is None:
306 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700307 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400308 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700309 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700310 if proc.returncode != 0:
311 raise ExternalError(
312 "Failed to run command '{}' (exit code {}):\n{}".format(
313 args, proc.returncode, output))
314 return output
315
316
Tao Baoc765cca2018-01-31 17:32:40 -0800317def RoundUpTo4K(value):
318 rounded_up = value + 4095
319 return rounded_up - (rounded_up % 4096)
320
321
Ying Wang7e6d4e42010-12-13 16:25:36 -0800322def CloseInheritedPipes():
323 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
324 before doing other work."""
325 if platform.system() != "Darwin":
326 return
327 for d in range(3, 1025):
328 try:
329 stat = os.fstat(d)
330 if stat is not None:
331 pipebit = stat[0] & 0x1000
332 if pipebit != 0:
333 os.close(d)
334 except OSError:
335 pass
336
337
Tao Bao1c320f82019-10-04 23:25:12 -0700338class BuildInfo(object):
339 """A class that holds the information for a given build.
340
341 This class wraps up the property querying for a given source or target build.
342 It abstracts away the logic of handling OEM-specific properties, and caches
343 the commonly used properties such as fingerprint.
344
345 There are two types of info dicts: a) build-time info dict, which is generated
346 at build time (i.e. included in a target_files zip); b) OEM info dict that is
347 specified at package generation time (via command line argument
348 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
349 having "oem_fingerprint_properties" in build-time info dict), all the queries
350 would be answered based on build-time info dict only. Otherwise if using
351 OEM-specific properties, some of them will be calculated from two info dicts.
352
353 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800354 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700355
356 Attributes:
357 info_dict: The build-time info dict.
358 is_ab: Whether it's a build that uses A/B OTA.
359 oem_dicts: A list of OEM dicts.
360 oem_props: A list of OEM properties that should be read from OEM dicts; None
361 if the build doesn't use any OEM-specific property.
362 fingerprint: The fingerprint of the build, which would be calculated based
363 on OEM properties if applicable.
364 device: The device name, which could come from OEM dicts if applicable.
365 """
366
367 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
368 "ro.product.manufacturer", "ro.product.model",
369 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700370 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
371 "product", "odm", "vendor", "system_ext", "system"]
372 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
373 "product", "product_services", "odm", "vendor", "system"]
374 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700375
Tianjiefdda51d2021-05-05 14:46:35 -0700376 # The length of vbmeta digest to append to the fingerprint
377 _VBMETA_DIGEST_SIZE_USED = 8
378
379 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700380 """Initializes a BuildInfo instance with the given dicts.
381
382 Note that it only wraps up the given dicts, without making copies.
383
384 Arguments:
385 info_dict: The build-time info dict.
386 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
387 that it always uses the first dict to calculate the fingerprint or the
388 device name. The rest would be used for asserting OEM properties only
389 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700390 use_legacy_id: Use the legacy build id to construct the fingerprint. This
391 is used when we need a BuildInfo class, while the vbmeta digest is
392 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700393
394 Raises:
395 ValueError: On invalid inputs.
396 """
397 self.info_dict = info_dict
398 self.oem_dicts = oem_dicts
399
400 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700401 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700402
Hongguang Chend7c160f2020-05-03 21:24:26 -0700403 # Skip _oem_props if oem_dicts is None to use BuildInfo in
404 # sign_target_files_apks
405 if self.oem_dicts:
406 self._oem_props = info_dict.get("oem_fingerprint_properties")
407 else:
408 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700409
Daniel Normand5fe8622020-01-08 17:01:11 -0800410 def check_fingerprint(fingerprint):
411 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
412 raise ValueError(
413 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
414 "3.2.2. Build Parameters.".format(fingerprint))
415
Daniel Normand5fe8622020-01-08 17:01:11 -0800416 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800417 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800418 try:
419 fingerprint = self.CalculatePartitionFingerprint(partition)
420 check_fingerprint(fingerprint)
421 self._partition_fingerprints[partition] = fingerprint
422 except ExternalError:
423 continue
424 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800425 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800426 # need a fingerprint when creating the image.
427 self._partition_fingerprints[
428 "system_other"] = self._partition_fingerprints["system"]
429
Tao Bao1c320f82019-10-04 23:25:12 -0700430 # These two should be computed only after setting self._oem_props.
431 self._device = self.GetOemProperty("ro.product.device")
432 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800433 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700434
435 @property
436 def is_ab(self):
437 return self._is_ab
438
439 @property
440 def device(self):
441 return self._device
442
443 @property
444 def fingerprint(self):
445 return self._fingerprint
446
447 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400448 def is_vabc(self):
449 vendor_prop = self.info_dict.get("vendor.build.prop")
450 vabc_enabled = vendor_prop and \
451 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
452 return vabc_enabled
453
454 @property
Kelvin Zhangad427382021-08-12 16:19:09 -0700455 def is_vabc_xor(self):
456 vendor_prop = self.info_dict.get("vendor.build.prop")
457 vabc_xor_enabled = vendor_prop and \
458 vendor_prop.GetProp("ro.virtual_ab.compression.xor.enabled") == "true"
459 return vabc_xor_enabled
460
461 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400462 def vendor_suppressed_vabc(self):
463 vendor_prop = self.info_dict.get("vendor.build.prop")
464 vabc_suppressed = vendor_prop and \
465 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
466 return vabc_suppressed and vabc_suppressed.lower() == "true"
467
468 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700469 def oem_props(self):
470 return self._oem_props
471
Kelvin Zhanga19fb312021-07-26 14:05:02 -0400472 @property
473 def avb_enabled(self):
474 return self.get("avb_enable") == "true"
475
Tao Bao1c320f82019-10-04 23:25:12 -0700476 def __getitem__(self, key):
477 return self.info_dict[key]
478
479 def __setitem__(self, key, value):
480 self.info_dict[key] = value
481
482 def get(self, key, default=None):
483 return self.info_dict.get(key, default)
484
485 def items(self):
486 return self.info_dict.items()
487
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000488 def _GetRawBuildProp(self, prop, partition):
489 prop_file = '{}.build.prop'.format(
490 partition) if partition else 'build.prop'
491 partition_props = self.info_dict.get(prop_file)
492 if not partition_props:
493 return None
494 return partition_props.GetProp(prop)
495
Daniel Normand5fe8622020-01-08 17:01:11 -0800496 def GetPartitionBuildProp(self, prop, partition):
497 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800498
499 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400500 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800501
Daniel Normand5fe8622020-01-08 17:01:11 -0800502 # If provided a partition for this property, only look within that
503 # partition's build.prop.
504 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800505 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800506 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800507 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000508
509 prop_val = self._GetRawBuildProp(prop, partition)
510 if prop_val is not None:
511 return prop_val
512 raise ExternalError("couldn't find %s in %s.build.prop" %
513 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800514
Tao Bao1c320f82019-10-04 23:25:12 -0700515 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800516 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700517 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
518 return self._ResolveRoProductBuildProp(prop)
519
Tianjiefdda51d2021-05-05 14:46:35 -0700520 if prop == "ro.build.id":
521 return self._GetBuildId()
522
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000523 prop_val = self._GetRawBuildProp(prop, None)
524 if prop_val is not None:
525 return prop_val
526
527 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700528
529 def _ResolveRoProductBuildProp(self, prop):
530 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000531 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700532 if prop_val:
533 return prop_val
534
Steven Laver8e2086e2020-04-27 16:26:31 -0700535 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000536 source_order_val = self._GetRawBuildProp(
537 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700538 if source_order_val:
539 source_order = source_order_val.split(",")
540 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700541 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700542
543 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700544 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700545 raise ExternalError(
546 "Invalid ro.product.property_source_order '{}'".format(source_order))
547
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000548 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700549 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000550 "ro.product", "ro.product.{}".format(source_partition), 1)
551 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700552 if prop_val:
553 return prop_val
554
555 raise ExternalError("couldn't resolve {}".format(prop))
556
Steven Laver8e2086e2020-04-27 16:26:31 -0700557 def _GetRoProductPropsDefaultSourceOrder(self):
558 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
559 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000560 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700561 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000562 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700563 if android_version == "10":
564 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
565 # NOTE: float() conversion of android_version will have rounding error.
566 # We are checking for "9" or less, and using "< 10" is well outside of
567 # possible floating point rounding.
568 try:
569 android_version_val = float(android_version)
570 except ValueError:
571 android_version_val = 0
572 if android_version_val < 10:
573 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
574 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
575
Tianjieb37c5be2020-10-15 21:27:10 -0700576 def _GetPlatformVersion(self):
577 version_sdk = self.GetBuildProp("ro.build.version.sdk")
578 # init code switches to version_release_or_codename (see b/158483506). After
579 # API finalization, release_or_codename will be the same as release. This
580 # is the best effort to support pre-S dev stage builds.
581 if int(version_sdk) >= 30:
582 try:
583 return self.GetBuildProp("ro.build.version.release_or_codename")
584 except ExternalError:
585 logger.warning('Failed to find ro.build.version.release_or_codename')
586
587 return self.GetBuildProp("ro.build.version.release")
588
Tianjiefdda51d2021-05-05 14:46:35 -0700589 def _GetBuildId(self):
590 build_id = self._GetRawBuildProp("ro.build.id", None)
591 if build_id:
592 return build_id
593
594 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
595 if not legacy_build_id:
596 raise ExternalError("Couldn't find build id in property file")
597
598 if self.use_legacy_id:
599 return legacy_build_id
600
601 # Append the top 8 chars of vbmeta digest to the existing build id. The
602 # logic needs to match the one in init, so that OTA can deliver correctly.
603 avb_enable = self.info_dict.get("avb_enable") == "true"
604 if not avb_enable:
605 raise ExternalError("AVB isn't enabled when using legacy build id")
606
607 vbmeta_digest = self.info_dict.get("vbmeta_digest")
608 if not vbmeta_digest:
609 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
610 " id")
611 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
612 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
613
614 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
615 return legacy_build_id + '.' + digest_prefix
616
Tianjieb37c5be2020-10-15 21:27:10 -0700617 def _GetPartitionPlatformVersion(self, partition):
618 try:
619 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
620 partition)
621 except ExternalError:
622 return self.GetPartitionBuildProp("ro.build.version.release",
623 partition)
624
Tao Bao1c320f82019-10-04 23:25:12 -0700625 def GetOemProperty(self, key):
626 if self.oem_props is not None and key in self.oem_props:
627 return self.oem_dicts[0][key]
628 return self.GetBuildProp(key)
629
Daniel Normand5fe8622020-01-08 17:01:11 -0800630 def GetPartitionFingerprint(self, partition):
631 return self._partition_fingerprints.get(partition, None)
632
633 def CalculatePartitionFingerprint(self, partition):
634 try:
635 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
636 except ExternalError:
637 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
638 self.GetPartitionBuildProp("ro.product.brand", partition),
639 self.GetPartitionBuildProp("ro.product.name", partition),
640 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700641 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800642 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400643 self.GetPartitionBuildProp(
644 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800645 self.GetPartitionBuildProp("ro.build.type", partition),
646 self.GetPartitionBuildProp("ro.build.tags", partition))
647
Tao Bao1c320f82019-10-04 23:25:12 -0700648 def CalculateFingerprint(self):
649 if self.oem_props is None:
650 try:
651 return self.GetBuildProp("ro.build.fingerprint")
652 except ExternalError:
653 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
654 self.GetBuildProp("ro.product.brand"),
655 self.GetBuildProp("ro.product.name"),
656 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700657 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700658 self.GetBuildProp("ro.build.id"),
659 self.GetBuildProp("ro.build.version.incremental"),
660 self.GetBuildProp("ro.build.type"),
661 self.GetBuildProp("ro.build.tags"))
662 return "%s/%s/%s:%s" % (
663 self.GetOemProperty("ro.product.brand"),
664 self.GetOemProperty("ro.product.name"),
665 self.GetOemProperty("ro.product.device"),
666 self.GetBuildProp("ro.build.thumbprint"))
667
668 def WriteMountOemScript(self, script):
669 assert self.oem_props is not None
670 recovery_mount_options = self.info_dict.get("recovery_mount_options")
671 script.Mount("/oem", recovery_mount_options)
672
673 def WriteDeviceAssertions(self, script, oem_no_mount):
674 # Read the property directly if not using OEM properties.
675 if not self.oem_props:
676 script.AssertDevice(self.device)
677 return
678
679 # Otherwise assert OEM properties.
680 if not self.oem_dicts:
681 raise ExternalError(
682 "No OEM file provided to answer expected assertions")
683
684 for prop in self.oem_props.split():
685 values = []
686 for oem_dict in self.oem_dicts:
687 if prop in oem_dict:
688 values.append(oem_dict[prop])
689 if not values:
690 raise ExternalError(
691 "The OEM file is missing the property %s" % (prop,))
692 script.AssertOemProperty(prop, values, oem_no_mount)
693
694
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000695def ReadFromInputFile(input_file, fn):
696 """Reads the contents of fn from input zipfile or directory."""
697 if isinstance(input_file, zipfile.ZipFile):
698 return input_file.read(fn).decode()
699 else:
700 path = os.path.join(input_file, *fn.split("/"))
701 try:
702 with open(path) as f:
703 return f.read()
704 except IOError as e:
705 if e.errno == errno.ENOENT:
706 raise KeyError(fn)
707
708
Yifan Hong10482a22021-01-07 14:38:41 -0800709def ExtractFromInputFile(input_file, fn):
710 """Extracts the contents of fn from input zipfile or directory into a file."""
711 if isinstance(input_file, zipfile.ZipFile):
712 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500713 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800714 f.write(input_file.read(fn))
715 return tmp_file
716 else:
717 file = os.path.join(input_file, *fn.split("/"))
718 if not os.path.exists(file):
719 raise KeyError(fn)
720 return file
721
Kelvin Zhang563750f2021-04-28 12:46:17 -0400722
jiajia tangf3f842b2021-03-17 21:49:44 +0800723class RamdiskFormat(object):
724 LZ4 = 1
725 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800726
Kelvin Zhang563750f2021-04-28 12:46:17 -0400727
jiajia tang836f76b2021-04-02 14:48:26 +0800728def _GetRamdiskFormat(info_dict):
729 if info_dict.get('lz4_ramdisks') == 'true':
730 ramdisk_format = RamdiskFormat.LZ4
731 else:
732 ramdisk_format = RamdiskFormat.GZ
733 return ramdisk_format
734
Kelvin Zhang563750f2021-04-28 12:46:17 -0400735
Tao Bao410ad8b2018-08-24 12:08:38 -0700736def LoadInfoDict(input_file, repacking=False):
737 """Loads the key/value pairs from the given input target_files.
738
Tianjiea85bdf02020-07-29 11:56:19 -0700739 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700740 checks and returns the parsed key/value pairs for to the given build. It's
741 usually called early when working on input target_files files, e.g. when
742 generating OTAs, or signing builds. Note that the function may be called
743 against an old target_files file (i.e. from past dessert releases). So the
744 property parsing needs to be backward compatible.
745
746 In a `META/misc_info.txt`, a few properties are stored as links to the files
747 in the PRODUCT_OUT directory. It works fine with the build system. However,
748 they are no longer available when (re)generating images from target_files zip.
749 When `repacking` is True, redirect these properties to the actual files in the
750 unzipped directory.
751
752 Args:
753 input_file: The input target_files file, which could be an open
754 zipfile.ZipFile instance, or a str for the dir that contains the files
755 unzipped from a target_files file.
756 repacking: Whether it's trying repack an target_files file after loading the
757 info dict (default: False). If so, it will rewrite a few loaded
758 properties (e.g. selinux_fc, root_dir) to point to the actual files in
759 target_files file. When doing repacking, `input_file` must be a dir.
760
761 Returns:
762 A dict that contains the parsed key/value pairs.
763
764 Raises:
765 AssertionError: On invalid input arguments.
766 ValueError: On malformed input values.
767 """
768 if repacking:
769 assert isinstance(input_file, str), \
770 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700771
Doug Zongkerc9253822014-02-04 12:17:58 -0800772 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000773 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800774
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700775 try:
Michael Runge6e836112014-04-15 17:40:21 -0700776 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700777 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700778 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700779
Tao Bao410ad8b2018-08-24 12:08:38 -0700780 if "recovery_api_version" not in d:
781 raise ValueError("Failed to find 'recovery_api_version'")
782 if "fstab_version" not in d:
783 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800784
Tao Bao410ad8b2018-08-24 12:08:38 -0700785 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700786 # "selinux_fc" properties should point to the file_contexts files
787 # (file_contexts.bin) under META/.
788 for key in d:
789 if key.endswith("selinux_fc"):
790 fc_basename = os.path.basename(d[key])
791 fc_config = os.path.join(input_file, "META", fc_basename)
792 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700793
Daniel Norman72c626f2019-05-13 15:58:14 -0700794 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700795
Tom Cherryd14b8952018-08-09 14:26:00 -0700796 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700797 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700798 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700799 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700800
David Anderson0ec64ac2019-12-06 12:21:18 -0800801 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700802 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700803 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800804 key_name = part_name + "_base_fs_file"
805 if key_name not in d:
806 continue
807 basename = os.path.basename(d[key_name])
808 base_fs_file = os.path.join(input_file, "META", basename)
809 if os.path.exists(base_fs_file):
810 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700811 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700812 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800813 "Failed to find %s base fs file: %s", part_name, base_fs_file)
814 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700815
Doug Zongker37974732010-09-16 17:44:38 -0700816 def makeint(key):
817 if key in d:
818 d[key] = int(d[key], 0)
819
820 makeint("recovery_api_version")
821 makeint("blocksize")
822 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700823 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700824 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700825 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700826 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800827 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700828
Steve Muckle903a1ca2020-05-07 17:32:10 -0700829 boot_images = "boot.img"
830 if "boot_images" in d:
831 boot_images = d["boot_images"]
832 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400833 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700834
Tao Bao765668f2019-10-04 22:03:00 -0700835 # Load recovery fstab if applicable.
836 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800837 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800838
Tianjie Xu861f4132018-09-12 11:49:33 -0700839 # Tries to load the build props for all partitions with care_map, including
840 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800841 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800842 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000843 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800844 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700845 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800846
Tao Bao3ed35d32019-10-07 20:48:48 -0700847 # Set up the salt (based on fingerprint) that will be used when adding AVB
848 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800849 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700850 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800851 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800852 fingerprint = build_info.GetPartitionFingerprint(partition)
853 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400854 d["avb_{}_salt".format(partition)] = sha256(
855 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700856
857 # Set the vbmeta digest if exists
858 try:
859 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
860 except KeyError:
861 pass
862
Kelvin Zhang39aea442020-08-17 11:04:25 -0400863 try:
864 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
865 except KeyError:
866 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700867 return d
868
Tao Baod1de6f32017-03-01 16:38:48 -0800869
Daniel Norman4cc9df62019-07-18 10:11:07 -0700870def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900871 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700872 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900873
Daniel Norman4cc9df62019-07-18 10:11:07 -0700874
875def LoadDictionaryFromFile(file_path):
876 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900877 return LoadDictionaryFromLines(lines)
878
879
Michael Runge6e836112014-04-15 17:40:21 -0700880def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700881 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700882 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700883 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700884 if not line or line.startswith("#"):
885 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700886 if "=" in line:
887 name, value = line.split("=", 1)
888 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700889 return d
890
Tao Baod1de6f32017-03-01 16:38:48 -0800891
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000892class PartitionBuildProps(object):
893 """The class holds the build prop of a particular partition.
894
895 This class loads the build.prop and holds the build properties for a given
896 partition. It also partially recognizes the 'import' statement in the
897 build.prop; and calculates alternative values of some specific build
898 properties during runtime.
899
900 Attributes:
901 input_file: a zipped target-file or an unzipped target-file directory.
902 partition: name of the partition.
903 props_allow_override: a list of build properties to search for the
904 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000905 build_props: a dict of build properties for the given partition.
906 prop_overrides: a set of props that are overridden by import.
907 placeholder_values: A dict of runtime variables' values to replace the
908 placeholders in the build.prop file. We expect exactly one value for
909 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800910 ramdisk_format: If name is "boot", the format of ramdisk inside the
911 boot image. Otherwise, its value is ignored.
912 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000913 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400914
Tianjie Xu9afb2212020-05-10 21:48:15 +0000915 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000916 self.input_file = input_file
917 self.partition = name
918 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000919 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000920 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000921 self.prop_overrides = set()
922 self.placeholder_values = {}
923 if placeholder_values:
924 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000925
926 @staticmethod
927 def FromDictionary(name, build_props):
928 """Constructs an instance from a build prop dictionary."""
929
930 props = PartitionBuildProps("unknown", name)
931 props.build_props = build_props.copy()
932 return props
933
934 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800935 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000936 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800937
Devin Mooreafdd7c72021-12-13 22:04:08 +0000938 if name in ("boot", "init_boot"):
Kelvin Zhang563750f2021-04-28 12:46:17 -0400939 data = PartitionBuildProps._ReadBootPropFile(
Devin Mooreafdd7c72021-12-13 22:04:08 +0000940 input_file, name, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800941 else:
942 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
943
944 props = PartitionBuildProps(input_file, name, placeholder_values)
945 props._LoadBuildProp(data)
946 return props
947
948 @staticmethod
Devin Mooreafdd7c72021-12-13 22:04:08 +0000949 def _ReadBootPropFile(input_file, partition_name, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800950 """
951 Read build.prop for boot image from input_file.
952 Return empty string if not found.
953 """
Devin Mooreafdd7c72021-12-13 22:04:08 +0000954 image_path = 'IMAGES/' + partition_name + '.img'
Yifan Hong10482a22021-01-07 14:38:41 -0800955 try:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000956 boot_img = ExtractFromInputFile(input_file, image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800957 except KeyError:
Devin Mooreafdd7c72021-12-13 22:04:08 +0000958 logger.warning('Failed to read %s', image_path)
Yifan Hong10482a22021-01-07 14:38:41 -0800959 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800960 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800961 if prop_file is None:
962 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500963 with open(prop_file, "r") as f:
964 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800965
966 @staticmethod
967 def _ReadPartitionPropFile(input_file, name):
968 """
969 Read build.prop for name from input_file.
970 Return empty string if not found.
971 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000972 data = ''
973 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
974 '{}/build.prop'.format(name.upper())]:
975 try:
976 data = ReadFromInputFile(input_file, prop_file)
977 break
978 except KeyError:
979 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800980 if data == '':
981 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800982 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000983
Yifan Hong125d0b62020-09-24 17:07:03 -0700984 @staticmethod
985 def FromBuildPropFile(name, build_prop_file):
986 """Constructs an instance from a build prop file."""
987
988 props = PartitionBuildProps("unknown", name)
989 with open(build_prop_file) as f:
990 props._LoadBuildProp(f.read())
991 return props
992
Tianjie Xu9afb2212020-05-10 21:48:15 +0000993 def _LoadBuildProp(self, data):
994 for line in data.split('\n'):
995 line = line.strip()
996 if not line or line.startswith("#"):
997 continue
998 if line.startswith("import"):
999 overrides = self._ImportParser(line)
1000 duplicates = self.prop_overrides.intersection(overrides.keys())
1001 if duplicates:
1002 raise ValueError('prop {} is overridden multiple times'.format(
1003 ','.join(duplicates)))
1004 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1005 self.build_props.update(overrides)
1006 elif "=" in line:
1007 name, value = line.split("=", 1)
1008 if name in self.prop_overrides:
1009 raise ValueError('prop {} is set again after overridden by import '
1010 'statement'.format(name))
1011 self.build_props[name] = value
1012
1013 def _ImportParser(self, line):
1014 """Parses the build prop in a given import statement."""
1015
1016 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001017 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001018 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001019
1020 if len(tokens) == 3:
1021 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1022 return {}
1023
Tianjie Xu9afb2212020-05-10 21:48:15 +00001024 import_path = tokens[1]
1025 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1026 raise ValueError('Unrecognized import path {}'.format(line))
1027
1028 # We only recognize a subset of import statement that the init process
1029 # supports. And we can loose the restriction based on how the dynamic
1030 # fingerprint is used in practice. The placeholder format should be
1031 # ${placeholder}, and its value should be provided by the caller through
1032 # the placeholder_values.
1033 for prop, value in self.placeholder_values.items():
1034 prop_place_holder = '${{{}}}'.format(prop)
1035 if prop_place_holder in import_path:
1036 import_path = import_path.replace(prop_place_holder, value)
1037 if '$' in import_path:
1038 logger.info('Unresolved place holder in import path %s', import_path)
1039 return {}
1040
1041 import_path = import_path.replace('/{}'.format(self.partition),
1042 self.partition.upper())
1043 logger.info('Parsing build props override from %s', import_path)
1044
1045 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1046 d = LoadDictionaryFromLines(lines)
1047 return {key: val for key, val in d.items()
1048 if key in self.props_allow_override}
1049
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001050 def GetProp(self, prop):
1051 return self.build_props.get(prop)
1052
1053
Tianjie Xucfa86222016-03-07 16:31:19 -08001054def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1055 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001056 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001057 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001058 self.mount_point = mount_point
1059 self.fs_type = fs_type
1060 self.device = device
1061 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001062 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001063 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001064
1065 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001066 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001067 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001068 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001069 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001070
Tao Baod1de6f32017-03-01 16:38:48 -08001071 assert fstab_version == 2
1072
1073 d = {}
1074 for line in data.split("\n"):
1075 line = line.strip()
1076 if not line or line.startswith("#"):
1077 continue
1078
1079 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1080 pieces = line.split()
1081 if len(pieces) != 5:
1082 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1083
1084 # Ignore entries that are managed by vold.
1085 options = pieces[4]
1086 if "voldmanaged=" in options:
1087 continue
1088
1089 # It's a good line, parse it.
1090 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001091 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001092 options = options.split(",")
1093 for i in options:
1094 if i.startswith("length="):
1095 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001096 elif i == "slotselect":
1097 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001098 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001099 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001100 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001101
Tao Baod1de6f32017-03-01 16:38:48 -08001102 mount_flags = pieces[3]
1103 # Honor the SELinux context if present.
1104 context = None
1105 for i in mount_flags.split(","):
1106 if i.startswith("context="):
1107 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001108
Tao Baod1de6f32017-03-01 16:38:48 -08001109 mount_point = pieces[1]
1110 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001111 device=pieces[0], length=length, context=context,
1112 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001113
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001114 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001115 # system. Other areas assume system is always at "/system" so point /system
1116 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001117 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001118 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001119 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001120 return d
1121
1122
Tao Bao765668f2019-10-04 22:03:00 -07001123def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1124 """Finds the path to recovery fstab and loads its contents."""
1125 # recovery fstab is only meaningful when installing an update via recovery
1126 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001127 if info_dict.get('ab_update') == 'true' and \
1128 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001129 return None
1130
1131 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1132 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1133 # cases, since it may load the info_dict from an old build (e.g. when
1134 # generating incremental OTAs from that build).
1135 system_root_image = info_dict.get('system_root_image') == 'true'
1136 if info_dict.get('no_recovery') != 'true':
1137 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1138 if isinstance(input_file, zipfile.ZipFile):
1139 if recovery_fstab_path not in input_file.namelist():
1140 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1141 else:
1142 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1143 if not os.path.exists(path):
1144 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1145 return LoadRecoveryFSTab(
1146 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1147 system_root_image)
1148
1149 if info_dict.get('recovery_as_boot') == 'true':
1150 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1151 if isinstance(input_file, zipfile.ZipFile):
1152 if recovery_fstab_path not in input_file.namelist():
1153 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1154 else:
1155 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1156 if not os.path.exists(path):
1157 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1158 return LoadRecoveryFSTab(
1159 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1160 system_root_image)
1161
1162 return None
1163
1164
Doug Zongker37974732010-09-16 17:44:38 -07001165def DumpInfoDict(d):
1166 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001167 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001168
Dan Albert8b72aef2015-03-23 19:13:21 -07001169
Daniel Norman55417142019-11-25 16:04:36 -08001170def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001171 """Merges dynamic partition info variables.
1172
1173 Args:
1174 framework_dict: The dictionary of dynamic partition info variables from the
1175 partial framework target files.
1176 vendor_dict: The dictionary of dynamic partition info variables from the
1177 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001178
1179 Returns:
1180 The merged dynamic partition info dictionary.
1181 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001182
1183 def uniq_concat(a, b):
1184 combined = set(a.split(" "))
1185 combined.update(set(b.split(" ")))
1186 combined = [item.strip() for item in combined if item.strip()]
1187 return " ".join(sorted(combined))
1188
1189 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001190 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001191 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1192
1193 merged_dict = {"use_dynamic_partitions": "true"}
1194
1195 merged_dict["dynamic_partition_list"] = uniq_concat(
1196 framework_dict.get("dynamic_partition_list", ""),
1197 vendor_dict.get("dynamic_partition_list", ""))
1198
1199 # Super block devices are defined by the vendor dict.
1200 if "super_block_devices" in vendor_dict:
1201 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1202 for block_device in merged_dict["super_block_devices"].split(" "):
1203 key = "super_%s_device_size" % block_device
1204 if key not in vendor_dict:
1205 raise ValueError("Vendor dict does not contain required key %s." % key)
1206 merged_dict[key] = vendor_dict[key]
1207
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001208 # Partition groups and group sizes are defined by the vendor dict because
1209 # these values may vary for each board that uses a shared system image.
1210 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001211 for partition_group in merged_dict["super_partition_groups"].split(" "):
1212 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001213 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001214 if key not in vendor_dict:
1215 raise ValueError("Vendor dict does not contain required key %s." % key)
1216 merged_dict[key] = vendor_dict[key]
1217
1218 # Set the partition group's partition list using a concatenation of the
1219 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001220 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001221 merged_dict[key] = uniq_concat(
1222 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301223
Daniel Normanb0c75912020-09-24 14:30:21 -07001224 # Various other flags should be copied from the vendor dict, if defined.
1225 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1226 "super_metadata_device", "super_partition_error_limit",
1227 "super_partition_size"):
1228 if key in vendor_dict.keys():
1229 merged_dict[key] = vendor_dict[key]
1230
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001231 return merged_dict
1232
1233
Daniel Norman21c34f72020-11-11 17:25:50 -08001234def PartitionMapFromTargetFiles(target_files_dir):
1235 """Builds a map from partition -> path within an extracted target files directory."""
1236 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1237 possible_subdirs = {
1238 "system": ["SYSTEM"],
1239 "vendor": ["VENDOR", "SYSTEM/vendor"],
1240 "product": ["PRODUCT", "SYSTEM/product"],
1241 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1242 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1243 "vendor_dlkm": [
1244 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1245 ],
1246 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1247 }
1248 partition_map = {}
1249 for partition, subdirs in possible_subdirs.items():
1250 for subdir in subdirs:
1251 if os.path.exists(os.path.join(target_files_dir, subdir)):
1252 partition_map[partition] = subdir
1253 break
1254 return partition_map
1255
1256
Daniel Normand3351562020-10-29 12:33:11 -07001257def SharedUidPartitionViolations(uid_dict, partition_groups):
1258 """Checks for APK sharedUserIds that cross partition group boundaries.
1259
1260 This uses a single or merged build's shareduid_violation_modules.json
1261 output file, as generated by find_shareduid_violation.py or
1262 core/tasks/find-shareduid-violation.mk.
1263
1264 An error is defined as a sharedUserId that is found in a set of partitions
1265 that span more than one partition group.
1266
1267 Args:
1268 uid_dict: A dictionary created by using the standard json module to read a
1269 complete shareduid_violation_modules.json file.
1270 partition_groups: A list of groups, where each group is a list of
1271 partitions.
1272
1273 Returns:
1274 A list of error messages.
1275 """
1276 errors = []
1277 for uid, partitions in uid_dict.items():
1278 found_in_groups = [
1279 group for group in partition_groups
1280 if set(partitions.keys()) & set(group)
1281 ]
1282 if len(found_in_groups) > 1:
1283 errors.append(
1284 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1285 % (uid, ",".join(sorted(partitions.keys()))))
1286 return errors
1287
1288
Daniel Norman21c34f72020-11-11 17:25:50 -08001289def RunHostInitVerifier(product_out, partition_map):
1290 """Runs host_init_verifier on the init rc files within partitions.
1291
1292 host_init_verifier searches the etc/init path within each partition.
1293
1294 Args:
1295 product_out: PRODUCT_OUT directory, containing partition directories.
1296 partition_map: A map of partition name -> relative path within product_out.
1297 """
1298 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1299 cmd = ["host_init_verifier"]
1300 for partition, path in partition_map.items():
1301 if partition not in allowed_partitions:
1302 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1303 partition)
1304 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1305 # Add --property-contexts if the file exists on the partition.
1306 property_contexts = "%s_property_contexts" % (
1307 "plat" if partition == "system" else partition)
1308 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1309 property_contexts)
1310 if os.path.exists(property_contexts_path):
1311 cmd.append("--property-contexts=%s" % property_contexts_path)
1312 # Add the passwd file if the file exists on the partition.
1313 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1314 if os.path.exists(passwd_path):
1315 cmd.extend(["-p", passwd_path])
1316 return RunAndCheckOutput(cmd)
1317
1318
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001319def AppendAVBSigningArgs(cmd, partition):
1320 """Append signing arguments for avbtool."""
1321 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1322 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001323 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1324 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1325 if os.path.exists(new_key_path):
1326 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001327 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1328 if key_path and algorithm:
1329 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001330 avb_salt = OPTIONS.info_dict.get("avb_salt")
1331 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001332 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001333 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001334
1335
Tao Bao765668f2019-10-04 22:03:00 -07001336def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001337 """Returns the VBMeta arguments for partition.
1338
1339 It sets up the VBMeta argument by including the partition descriptor from the
1340 given 'image', or by configuring the partition as a chained partition.
1341
1342 Args:
1343 partition: The name of the partition (e.g. "system").
1344 image: The path to the partition image.
1345 info_dict: A dict returned by common.LoadInfoDict(). Will use
1346 OPTIONS.info_dict if None has been given.
1347
1348 Returns:
1349 A list of VBMeta arguments.
1350 """
1351 if info_dict is None:
1352 info_dict = OPTIONS.info_dict
1353
1354 # Check if chain partition is used.
1355 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001356 if not key_path:
1357 return ["--include_descriptors_from_image", image]
1358
1359 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1360 # into vbmeta.img. The recovery image will be configured on an independent
1361 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1362 # See details at
1363 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001364 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001365 return []
1366
1367 # Otherwise chain the partition into vbmeta.
1368 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1369 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001370
1371
Tao Bao02a08592018-07-22 12:40:45 -07001372def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1373 """Constructs and returns the arg to build or verify a chained partition.
1374
1375 Args:
1376 partition: The partition name.
1377 info_dict: The info dict to look up the key info and rollback index
1378 location.
1379 key: The key to be used for building or verifying the partition. Defaults to
1380 the key listed in info_dict.
1381
1382 Returns:
1383 A string of form "partition:rollback_index_location:key" that can be used to
1384 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001385 """
1386 if key is None:
1387 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001388 if key and not os.path.exists(key) and OPTIONS.search_path:
1389 new_key_path = os.path.join(OPTIONS.search_path, key)
1390 if os.path.exists(new_key_path):
1391 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001392 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001393 rollback_index_location = info_dict[
1394 "avb_" + partition + "_rollback_index_location"]
1395 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1396
1397
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001398def AppendGkiSigningArgs(cmd):
1399 """Append GKI signing arguments for mkbootimg."""
1400 # e.g., --gki_signing_key path/to/signing_key
1401 # --gki_signing_algorithm SHA256_RSA4096"
1402
1403 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1404 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1405 if not key_path:
1406 return
1407
1408 if not os.path.exists(key_path) and OPTIONS.search_path:
1409 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1410 if os.path.exists(new_key_path):
1411 key_path = new_key_path
1412
1413 # Checks key_path exists, before appending --gki_signing_* args.
1414 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001415 raise ExternalError(
1416 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001417
1418 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1419 if key_path and algorithm:
1420 cmd.extend(["--gki_signing_key", key_path,
1421 "--gki_signing_algorithm", algorithm])
1422
1423 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1424 if signature_args:
1425 cmd.extend(["--gki_signing_signature_args", signature_args])
1426
1427
Daniel Norman276f0622019-07-26 14:13:51 -07001428def BuildVBMeta(image_path, partitions, name, needed_partitions):
1429 """Creates a VBMeta image.
1430
1431 It generates the requested VBMeta image. The requested image could be for
1432 top-level or chained VBMeta image, which is determined based on the name.
1433
1434 Args:
1435 image_path: The output path for the new VBMeta image.
1436 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001437 values. Only valid partition names are accepted, as partitions listed
1438 in common.AVB_PARTITIONS and custom partitions listed in
1439 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001440 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1441 needed_partitions: Partitions whose descriptors should be included into the
1442 generated VBMeta image.
1443
1444 Raises:
1445 AssertionError: On invalid input args.
1446 """
1447 avbtool = OPTIONS.info_dict["avb_avbtool"]
1448 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1449 AppendAVBSigningArgs(cmd, name)
1450
Hongguang Chenf23364d2020-04-27 18:36:36 -07001451 custom_partitions = OPTIONS.info_dict.get(
1452 "avb_custom_images_partition_list", "").strip().split()
1453
Daniel Norman276f0622019-07-26 14:13:51 -07001454 for partition, path in partitions.items():
1455 if partition not in needed_partitions:
1456 continue
1457 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001458 partition in AVB_VBMETA_PARTITIONS or
1459 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001460 'Unknown partition: {}'.format(partition)
1461 assert os.path.exists(path), \
1462 'Failed to find {} for {}'.format(path, partition)
1463 cmd.extend(GetAvbPartitionArg(partition, path))
1464
1465 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1466 if args and args.strip():
1467 split_args = shlex.split(args)
1468 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001469 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001470 # as a path relative to source tree, which may not be available at the
1471 # same location when running this script (we have the input target_files
1472 # zip only). For such cases, we additionally scan other locations (e.g.
1473 # IMAGES/, RADIO/, etc) before bailing out.
1474 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001475 chained_image = split_args[index + 1]
1476 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001477 continue
1478 found = False
1479 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1480 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001481 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001482 if os.path.exists(alt_path):
1483 split_args[index + 1] = alt_path
1484 found = True
1485 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001486 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001487 cmd.extend(split_args)
1488
1489 RunAndCheckOutput(cmd)
1490
1491
jiajia tang836f76b2021-04-02 14:48:26 +08001492def _MakeRamdisk(sourcedir, fs_config_file=None,
1493 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001494 ramdisk_img = tempfile.NamedTemporaryFile()
1495
1496 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1497 cmd = ["mkbootfs", "-f", fs_config_file,
1498 os.path.join(sourcedir, "RAMDISK")]
1499 else:
1500 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1501 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001502 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001503 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001504 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001505 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001506 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001507 else:
1508 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001509
1510 p2.wait()
1511 p1.wait()
1512 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001513 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001514
1515 return ramdisk_img
1516
1517
Steve Muckle9793cf62020-04-08 18:27:00 -07001518def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001519 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001520 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001521
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001522 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001523 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1524 we are building a two-step special image (i.e. building a recovery image to
1525 be loaded into /boot in two-step OTAs).
1526
1527 Return the image data, or None if sourcedir does not appear to contains files
1528 for building the requested image.
1529 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001530
Yifan Hong63c5ca12020-10-08 11:54:02 -07001531 if info_dict is None:
1532 info_dict = OPTIONS.info_dict
1533
Steve Muckle9793cf62020-04-08 18:27:00 -07001534 # "boot" or "recovery", without extension.
1535 partition_name = os.path.basename(sourcedir).lower()
1536
Yifan Hong63c5ca12020-10-08 11:54:02 -07001537 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001538 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001539 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1540 logger.info("Excluded kernel binary from recovery image.")
1541 else:
1542 kernel = "kernel"
Devin Mooreafdd7c72021-12-13 22:04:08 +00001543 elif partition_name == "init_boot":
1544 pass
Steve Muckle9793cf62020-04-08 18:27:00 -07001545 else:
1546 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001547 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001548 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001549 return None
1550
1551 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001552 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001553
Doug Zongkereef39442009-04-02 12:14:19 -07001554 img = tempfile.NamedTemporaryFile()
1555
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001556 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001557 ramdisk_format = _GetRamdiskFormat(info_dict)
1558 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1559 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001560
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001561 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1562 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1563
Yifan Hong63c5ca12020-10-08 11:54:02 -07001564 cmd = [mkbootimg]
1565 if kernel:
1566 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001567
Benoit Fradina45a8682014-07-14 21:00:43 +02001568 fn = os.path.join(sourcedir, "second")
1569 if os.access(fn, os.F_OK):
1570 cmd.append("--second")
1571 cmd.append(fn)
1572
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001573 fn = os.path.join(sourcedir, "dtb")
1574 if os.access(fn, os.F_OK):
1575 cmd.append("--dtb")
1576 cmd.append(fn)
1577
Doug Zongker171f1cd2009-06-15 22:36:37 -07001578 fn = os.path.join(sourcedir, "cmdline")
1579 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001580 cmd.append("--cmdline")
1581 cmd.append(open(fn).read().rstrip("\n"))
1582
1583 fn = os.path.join(sourcedir, "base")
1584 if os.access(fn, os.F_OK):
1585 cmd.append("--base")
1586 cmd.append(open(fn).read().rstrip("\n"))
1587
Ying Wang4de6b5b2010-08-25 14:29:34 -07001588 fn = os.path.join(sourcedir, "pagesize")
1589 if os.access(fn, os.F_OK):
1590 cmd.append("--pagesize")
1591 cmd.append(open(fn).read().rstrip("\n"))
1592
Steve Mucklef84668e2020-03-16 19:13:46 -07001593 if partition_name == "recovery":
1594 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301595 if not args:
1596 # Fall back to "mkbootimg_args" for recovery image
1597 # in case "recovery_mkbootimg_args" is not set.
1598 args = info_dict.get("mkbootimg_args")
Devin Mooreafdd7c72021-12-13 22:04:08 +00001599 elif partition_name == "init_boot":
1600 args = info_dict.get("mkbootimg_init_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001601 else:
1602 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001603 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001604 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001605
Tao Bao76def242017-11-21 09:25:31 -08001606 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001607 if args and args.strip():
1608 cmd.extend(shlex.split(args))
1609
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001610 if has_ramdisk:
1611 cmd.extend(["--ramdisk", ramdisk_img.name])
1612
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001613 AppendGkiSigningArgs(cmd)
1614
Tao Baod95e9fd2015-03-29 23:07:41 -07001615 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001616 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001617 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001618 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001619 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001620 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001621
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001622 if partition_name == "recovery":
1623 if info_dict.get("include_recovery_dtbo") == "true":
1624 fn = os.path.join(sourcedir, "recovery_dtbo")
1625 cmd.extend(["--recovery_dtbo", fn])
1626 if info_dict.get("include_recovery_acpio") == "true":
1627 fn = os.path.join(sourcedir, "recovery_acpio")
1628 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001629
Tao Bao986ee862018-10-04 15:46:16 -07001630 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001631
Tao Bao76def242017-11-21 09:25:31 -08001632 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001633 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001634 # Hard-code the path as "/boot" for two-step special recovery image (which
1635 # will be loaded into /boot during the two-step OTA).
1636 if two_step_image:
1637 path = "/boot"
1638 else:
Tao Baobf70c312017-07-11 17:27:55 -07001639 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001640 cmd = [OPTIONS.boot_signer_path]
1641 cmd.extend(OPTIONS.boot_signer_args)
1642 cmd.extend([path, img.name,
1643 info_dict["verity_key"] + ".pk8",
1644 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001645 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001646
Tao Baod95e9fd2015-03-29 23:07:41 -07001647 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001648 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001649 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001650 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001651 # We have switched from the prebuilt futility binary to using the tool
1652 # (futility-host) built from the source. Override the setting in the old
1653 # TF.zip.
1654 futility = info_dict["futility"]
1655 if futility.startswith("prebuilts/"):
1656 futility = "futility-host"
1657 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001658 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001659 info_dict["vboot_key"] + ".vbprivk",
1660 info_dict["vboot_subkey"] + ".vbprivk",
1661 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001662 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001663 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001664
Tao Baof3282b42015-04-01 11:21:55 -07001665 # Clean up the temp files.
1666 img_unsigned.close()
1667 img_keyblock.close()
1668
David Zeuthen8fecb282017-12-01 16:24:01 -05001669 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001670 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001671 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001672 if partition_name == "recovery":
1673 part_size = info_dict["recovery_size"]
1674 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001675 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001676 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001677 "--partition_size", str(part_size), "--partition_name",
1678 partition_name]
1679 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001680 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001681 if args and args.strip():
1682 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001683 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001684
1685 img.seek(os.SEEK_SET, 0)
1686 data = img.read()
1687
1688 if has_ramdisk:
1689 ramdisk_img.close()
1690 img.close()
1691
1692 return data
1693
1694
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001695def _SignBootableImage(image_path, prebuilt_name, partition_name,
1696 info_dict=None):
1697 """Performs AVB signing for a prebuilt boot.img.
1698
1699 Args:
1700 image_path: The full path of the image, e.g., /path/to/boot.img.
1701 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1702 boot-5.10.img, recovery.img.
1703 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1704 info_dict: The information dict read from misc_info.txt.
1705 """
1706 if info_dict is None:
1707 info_dict = OPTIONS.info_dict
1708
1709 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1710 if info_dict.get("avb_enable") == "true":
1711 avbtool = info_dict["avb_avbtool"]
1712 if partition_name == "recovery":
1713 part_size = info_dict["recovery_size"]
1714 else:
1715 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1716
1717 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1718 "--partition_size", str(part_size), "--partition_name",
1719 partition_name]
1720 AppendAVBSigningArgs(cmd, partition_name)
1721 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1722 if args and args.strip():
1723 cmd.extend(shlex.split(args))
1724 RunAndCheckOutput(cmd)
1725
1726
Doug Zongkerd5131602012-08-02 14:46:42 -07001727def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001728 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001729 """Return a File object with the desired bootable image.
1730
1731 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1732 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1733 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001734
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001735 if info_dict is None:
1736 info_dict = OPTIONS.info_dict
1737
Doug Zongker55d93282011-01-25 17:03:34 -08001738 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1739 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001740 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001741 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001742
1743 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1744 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001745 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001746 return File.FromLocalFile(name, prebuilt_path)
1747
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001748 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1749 if os.path.exists(prebuilt_path):
1750 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1751 signed_img = MakeTempFile()
1752 shutil.copy(prebuilt_path, signed_img)
1753 partition_name = tree_subdir.lower()
1754 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1755 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001756
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001757 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001758
1759 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Devin Mooreafdd7c72021-12-13 22:04:08 +00001760 # With init_boot == "true", we don't pack the ramdisk into boot.img.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001761 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1762 # for recovery.
Devin Mooreafdd7c72021-12-13 22:04:08 +00001763 has_ramdisk = ((info_dict.get("system_root_image") != "true" and
1764 info_dict.get("init_boot") != "true") or
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001765 prebuilt_name != "boot.img" or
1766 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001767
Doug Zongker6f1d0312014-08-22 08:07:12 -07001768 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001769 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001770 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001771 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001772 if data:
1773 return File(name, data)
1774 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001775
Doug Zongkereef39442009-04-02 12:14:19 -07001776
Steve Mucklee1b10862019-07-10 10:49:37 -07001777def _BuildVendorBootImage(sourcedir, info_dict=None):
1778 """Build a vendor boot image from the specified sourcedir.
1779
1780 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1781 turn them into a vendor boot image.
1782
1783 Return the image data, or None if sourcedir does not appear to contains files
1784 for building the requested image.
1785 """
1786
1787 if info_dict is None:
1788 info_dict = OPTIONS.info_dict
1789
1790 img = tempfile.NamedTemporaryFile()
1791
jiajia tang836f76b2021-04-02 14:48:26 +08001792 ramdisk_format = _GetRamdiskFormat(info_dict)
1793 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001794
1795 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1796 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1797
1798 cmd = [mkbootimg]
1799
1800 fn = os.path.join(sourcedir, "dtb")
1801 if os.access(fn, os.F_OK):
1802 cmd.append("--dtb")
1803 cmd.append(fn)
1804
1805 fn = os.path.join(sourcedir, "vendor_cmdline")
1806 if os.access(fn, os.F_OK):
1807 cmd.append("--vendor_cmdline")
1808 cmd.append(open(fn).read().rstrip("\n"))
1809
1810 fn = os.path.join(sourcedir, "base")
1811 if os.access(fn, os.F_OK):
1812 cmd.append("--base")
1813 cmd.append(open(fn).read().rstrip("\n"))
1814
1815 fn = os.path.join(sourcedir, "pagesize")
1816 if os.access(fn, os.F_OK):
1817 cmd.append("--pagesize")
1818 cmd.append(open(fn).read().rstrip("\n"))
1819
1820 args = info_dict.get("mkbootimg_args")
1821 if args and args.strip():
1822 cmd.extend(shlex.split(args))
1823
1824 args = info_dict.get("mkbootimg_version_args")
1825 if args and args.strip():
1826 cmd.extend(shlex.split(args))
1827
1828 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1829 cmd.extend(["--vendor_boot", img.name])
1830
Devin Moore50509012021-01-13 10:45:04 -08001831 fn = os.path.join(sourcedir, "vendor_bootconfig")
1832 if os.access(fn, os.F_OK):
1833 cmd.append("--vendor_bootconfig")
1834 cmd.append(fn)
1835
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001836 ramdisk_fragment_imgs = []
1837 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1838 if os.access(fn, os.F_OK):
1839 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1840 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001841 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1842 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001843 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001844 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1845 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001846 # Use prebuilt image if found, else create ramdisk from supplied files.
1847 if os.access(fn, os.F_OK):
1848 ramdisk_fragment_pathname = fn
1849 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001850 ramdisk_fragment_root = os.path.join(
1851 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001852 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1853 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001854 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1855 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1856 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1857
Steve Mucklee1b10862019-07-10 10:49:37 -07001858 RunAndCheckOutput(cmd)
1859
1860 # AVB: if enabled, calculate and add hash.
1861 if info_dict.get("avb_enable") == "true":
1862 avbtool = info_dict["avb_avbtool"]
1863 part_size = info_dict["vendor_boot_size"]
1864 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001865 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001866 AppendAVBSigningArgs(cmd, "vendor_boot")
1867 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1868 if args and args.strip():
1869 cmd.extend(shlex.split(args))
1870 RunAndCheckOutput(cmd)
1871
1872 img.seek(os.SEEK_SET, 0)
1873 data = img.read()
1874
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001875 for f in ramdisk_fragment_imgs:
1876 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001877 ramdisk_img.close()
1878 img.close()
1879
1880 return data
1881
1882
1883def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1884 info_dict=None):
1885 """Return a File object with the desired vendor boot image.
1886
1887 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1888 the source files in 'unpack_dir'/'tree_subdir'."""
1889
1890 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1891 if os.path.exists(prebuilt_path):
1892 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1893 return File.FromLocalFile(name, prebuilt_path)
1894
1895 logger.info("building image from target_files %s...", tree_subdir)
1896
1897 if info_dict is None:
1898 info_dict = OPTIONS.info_dict
1899
Kelvin Zhang0876c412020-06-23 15:06:58 -04001900 data = _BuildVendorBootImage(
1901 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001902 if data:
1903 return File(name, data)
1904 return None
1905
1906
Narayan Kamatha07bf042017-08-14 14:49:21 +01001907def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001908 """Gunzips the given gzip compressed file to a given output file."""
1909 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001910 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001911 shutil.copyfileobj(in_file, out_file)
1912
1913
Tao Bao0ff15de2019-03-20 11:26:06 -07001914def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001915 """Unzips the archive to the given directory.
1916
1917 Args:
1918 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001919 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001920 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1921 archvie. Non-matching patterns will be filtered out. If there's no match
1922 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001923 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001924 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001925 if patterns is not None:
1926 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001927 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001928 names = input_zip.namelist()
1929 filtered = [
1930 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1931
1932 # There isn't any matching files. Don't unzip anything.
1933 if not filtered:
1934 return
1935 cmd.extend(filtered)
1936
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001937 RunAndCheckOutput(cmd)
1938
1939
Daniel Norman78554ea2021-09-14 10:29:38 -07001940def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001941 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001942
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001943 Args:
1944 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1945 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1946
Daniel Norman78554ea2021-09-14 10:29:38 -07001947 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001948 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001949
Tao Bao1c830bf2017-12-25 10:43:47 -08001950 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001951 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001952 """
Doug Zongkereef39442009-04-02 12:14:19 -07001953
Tao Bao1c830bf2017-12-25 10:43:47 -08001954 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001955 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1956 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07001957 UnzipToDir(m.group(1), tmp, patterns)
1958 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08001959 filename = m.group(1)
1960 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07001961 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08001962
Tao Baodba59ee2018-01-09 13:21:02 -08001963 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001964
1965
Yifan Hong8a66a712019-04-04 15:37:57 -07001966def GetUserImage(which, tmpdir, input_zip,
1967 info_dict=None,
1968 allow_shared_blocks=None,
1969 hashtree_info_generator=None,
1970 reset_file_map=False):
1971 """Returns an Image object suitable for passing to BlockImageDiff.
1972
1973 This function loads the specified image from the given path. If the specified
1974 image is sparse, it also performs additional processing for OTA purpose. For
1975 example, it always adds block 0 to clobbered blocks list. It also detects
1976 files that cannot be reconstructed from the block list, for whom we should
1977 avoid applying imgdiff.
1978
1979 Args:
1980 which: The partition name.
1981 tmpdir: The directory that contains the prebuilt image and block map file.
1982 input_zip: The target-files ZIP archive.
1983 info_dict: The dict to be looked up for relevant info.
1984 allow_shared_blocks: If image is sparse, whether having shared blocks is
1985 allowed. If none, it is looked up from info_dict.
1986 hashtree_info_generator: If present and image is sparse, generates the
1987 hashtree_info for this sparse image.
1988 reset_file_map: If true and image is sparse, reset file map before returning
1989 the image.
1990 Returns:
1991 A Image object. If it is a sparse image and reset_file_map is False, the
1992 image will have file_map info loaded.
1993 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001994 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001995 info_dict = LoadInfoDict(input_zip)
1996
1997 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07001998 if info_dict.get(which + "_disable_sparse"):
1999 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07002000
2001 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2002 # shared blocks (i.e. some blocks will show up in multiple files' block
2003 # list). We can only allocate such shared blocks to the first "owner", and
2004 # disable imgdiff for all later occurrences.
2005 if allow_shared_blocks is None:
2006 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2007
2008 if is_sparse:
2009 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2010 hashtree_info_generator)
2011 if reset_file_map:
2012 img.ResetFileMap()
2013 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002014 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002015
2016
2017def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2018 """Returns a Image object suitable for passing to BlockImageDiff.
2019
2020 This function loads the specified non-sparse image from the given path.
2021
2022 Args:
2023 which: The partition name.
2024 tmpdir: The directory that contains the prebuilt image and block map file.
2025 Returns:
2026 A Image object.
2027 """
2028 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2029 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2030
2031 # The image and map files must have been created prior to calling
2032 # ota_from_target_files.py (since LMP).
2033 assert os.path.exists(path) and os.path.exists(mappath)
2034
Tianjie Xu41976c72019-07-03 13:57:01 -07002035 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2036
Yifan Hong8a66a712019-04-04 15:37:57 -07002037
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002038def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2039 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002040 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2041
2042 This function loads the specified sparse image from the given path, and
2043 performs additional processing for OTA purpose. For example, it always adds
2044 block 0 to clobbered blocks list. It also detects files that cannot be
2045 reconstructed from the block list, for whom we should avoid applying imgdiff.
2046
2047 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002048 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002049 tmpdir: The directory that contains the prebuilt image and block map file.
2050 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002051 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002052 hashtree_info_generator: If present, generates the hashtree_info for this
2053 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002054 Returns:
2055 A SparseImage object, with file_map info loaded.
2056 """
Tao Baoc765cca2018-01-31 17:32:40 -08002057 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2058 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2059
2060 # The image and map files must have been created prior to calling
2061 # ota_from_target_files.py (since LMP).
2062 assert os.path.exists(path) and os.path.exists(mappath)
2063
2064 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2065 # it to clobbered_blocks so that it will be written to the target
2066 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2067 clobbered_blocks = "0"
2068
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002069 image = sparse_img.SparseImage(
2070 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2071 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002072
2073 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2074 # if they contain all zeros. We can't reconstruct such a file from its block
2075 # list. Tag such entries accordingly. (Bug: 65213616)
2076 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002077 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002078 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002079 continue
2080
Tom Cherryd14b8952018-08-09 14:26:00 -07002081 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2082 # filename listed in system.map may contain an additional leading slash
2083 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2084 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002085 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002086 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002087 arcname = entry.lstrip('/')
2088 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002089 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002090 else:
2091 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002092
2093 assert arcname in input_zip.namelist(), \
2094 "Failed to find the ZIP entry for {}".format(entry)
2095
Tao Baoc765cca2018-01-31 17:32:40 -08002096 info = input_zip.getinfo(arcname)
2097 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002098
2099 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002100 # image, check the original block list to determine its completeness. Note
2101 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002102 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002103 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002104
Tao Baoc765cca2018-01-31 17:32:40 -08002105 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2106 ranges.extra['incomplete'] = True
2107
2108 return image
2109
2110
Doug Zongkereef39442009-04-02 12:14:19 -07002111def GetKeyPasswords(keylist):
2112 """Given a list of keys, prompt the user to enter passwords for
2113 those which require them. Return a {key: password} dict. password
2114 will be None if the key has no password."""
2115
Doug Zongker8ce7c252009-05-22 13:34:54 -07002116 no_passwords = []
2117 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002118 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002119 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002120
2121 # sorted() can't compare strings to None, so convert Nones to strings
2122 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002123 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002124 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002125 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002126 continue
2127
T.R. Fullhart37e10522013-03-18 10:31:26 -07002128 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002129 "-inform", "DER", "-nocrypt"],
2130 stdin=devnull.fileno(),
2131 stdout=devnull.fileno(),
2132 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002133 p.communicate()
2134 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002135 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002136 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002137 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002138 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2139 "-inform", "DER", "-passin", "pass:"],
2140 stdin=devnull.fileno(),
2141 stdout=devnull.fileno(),
2142 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002143 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002144 if p.returncode == 0:
2145 # Encrypted key with empty string as password.
2146 key_passwords[k] = ''
2147 elif stderr.startswith('Error decrypting key'):
2148 # Definitely encrypted key.
2149 # It would have said "Error reading key" if it didn't parse correctly.
2150 need_passwords.append(k)
2151 else:
2152 # Potentially, a type of key that openssl doesn't understand.
2153 # We'll let the routines in signapk.jar handle it.
2154 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002155 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002156
T.R. Fullhart37e10522013-03-18 10:31:26 -07002157 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002158 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002159 return key_passwords
2160
2161
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002162def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002163 """Gets the minSdkVersion declared in the APK.
2164
changho.shin0f125362019-07-08 10:59:00 +09002165 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002166 This can be both a decimal number (API Level) or a codename.
2167
2168 Args:
2169 apk_name: The APK filename.
2170
2171 Returns:
2172 The parsed SDK version string.
2173
2174 Raises:
2175 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002176 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002177 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002178 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002179 stderr=subprocess.PIPE)
2180 stdoutdata, stderrdata = proc.communicate()
2181 if proc.returncode != 0:
2182 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002183 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002184 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002185
Tao Baof47bf0f2018-03-21 23:28:51 -07002186 for line in stdoutdata.split("\n"):
2187 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002188 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2189 if m:
2190 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002191 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002192
2193
2194def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002195 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002196
Tao Baof47bf0f2018-03-21 23:28:51 -07002197 If minSdkVersion is set to a codename, it is translated to a number using the
2198 provided map.
2199
2200 Args:
2201 apk_name: The APK filename.
2202
2203 Returns:
2204 The parsed SDK version number.
2205
2206 Raises:
2207 ExternalError: On failing to get the min SDK version number.
2208 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002209 version = GetMinSdkVersion(apk_name)
2210 try:
2211 return int(version)
2212 except ValueError:
2213 # Not a decimal number. Codename?
2214 if version in codename_to_api_level_map:
2215 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002216 raise ExternalError(
2217 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2218 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002219
2220
2221def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002222 codename_to_api_level_map=None, whole_file=False,
2223 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002224 """Sign the input_name zip/jar/apk, producing output_name. Use the
2225 given key and password (the latter may be None if the key does not
2226 have a password.
2227
Doug Zongker951495f2009-08-14 12:44:19 -07002228 If whole_file is true, use the "-w" option to SignApk to embed a
2229 signature that covers the whole file in the archive comment of the
2230 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002231
2232 min_api_level is the API Level (int) of the oldest platform this file may end
2233 up on. If not specified for an APK, the API Level is obtained by interpreting
2234 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2235
2236 codename_to_api_level_map is needed to translate the codename which may be
2237 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002238
2239 Caller may optionally specify extra args to be passed to SignApk, which
2240 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002241 """
Tao Bao76def242017-11-21 09:25:31 -08002242 if codename_to_api_level_map is None:
2243 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002244 if extra_signapk_args is None:
2245 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002246
Alex Klyubin9667b182015-12-10 13:38:50 -08002247 java_library_path = os.path.join(
2248 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2249
Tao Baoe95540e2016-11-08 12:08:53 -08002250 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2251 ["-Djava.library.path=" + java_library_path,
2252 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002253 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002254 if whole_file:
2255 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002256
2257 min_sdk_version = min_api_level
2258 if min_sdk_version is None:
2259 if not whole_file:
2260 min_sdk_version = GetMinSdkVersionInt(
2261 input_name, codename_to_api_level_map)
2262 if min_sdk_version is not None:
2263 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2264
T.R. Fullhart37e10522013-03-18 10:31:26 -07002265 cmd.extend([key + OPTIONS.public_key_suffix,
2266 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002267 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002268
Tao Bao73dd4f42018-10-04 16:25:33 -07002269 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002270 if password is not None:
2271 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002272 stdoutdata, _ = proc.communicate(password)
2273 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002274 raise ExternalError(
2275 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002276 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002277
Doug Zongkereef39442009-04-02 12:14:19 -07002278
Doug Zongker37974732010-09-16 17:44:38 -07002279def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002280 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002281
Tao Bao9dd909e2017-11-14 11:27:32 -08002282 For non-AVB images, raise exception if the data is too big. Print a warning
2283 if the data is nearing the maximum size.
2284
2285 For AVB images, the actual image size should be identical to the limit.
2286
2287 Args:
2288 data: A string that contains all the data for the partition.
2289 target: The partition name. The ".img" suffix is optional.
2290 info_dict: The dict to be looked up for relevant info.
2291 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002292 if target.endswith(".img"):
2293 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002294 mount_point = "/" + target
2295
Ying Wangf8824af2014-06-03 14:07:27 -07002296 fs_type = None
2297 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002298 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002299 if mount_point == "/userdata":
2300 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002301 p = info_dict["fstab"][mount_point]
2302 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002303 device = p.device
2304 if "/" in device:
2305 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002306 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002307 if not fs_type or not limit:
2308 return
Doug Zongkereef39442009-04-02 12:14:19 -07002309
Andrew Boie0f9aec82012-02-14 09:32:52 -08002310 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002311 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2312 # path.
2313 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2314 if size != limit:
2315 raise ExternalError(
2316 "Mismatching image size for %s: expected %d actual %d" % (
2317 target, limit, size))
2318 else:
2319 pct = float(size) * 100.0 / limit
2320 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2321 if pct >= 99.0:
2322 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002323
2324 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002325 logger.warning("\n WARNING: %s\n", msg)
2326 else:
2327 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002328
2329
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002330def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002331 """Parses the APK certs info from a given target-files zip.
2332
2333 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2334 tuple with the following elements: (1) a dictionary that maps packages to
2335 certs (based on the "certificate" and "private_key" attributes in the file;
2336 (2) a string representing the extension of compressed APKs in the target files
2337 (e.g ".gz", ".bro").
2338
2339 Args:
2340 tf_zip: The input target_files ZipFile (already open).
2341
2342 Returns:
2343 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2344 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2345 no compressed APKs.
2346 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002347 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002348 compressed_extension = None
2349
Tao Bao0f990332017-09-08 19:02:54 -07002350 # META/apkcerts.txt contains the info for _all_ the packages known at build
2351 # time. Filter out the ones that are not installed.
2352 installed_files = set()
2353 for name in tf_zip.namelist():
2354 basename = os.path.basename(name)
2355 if basename:
2356 installed_files.add(basename)
2357
Tao Baoda30cfa2017-12-01 16:19:46 -08002358 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002359 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002360 if not line:
2361 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002362 m = re.match(
2363 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002364 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2365 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002366 line)
2367 if not m:
2368 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002369
Tao Bao818ddf52018-01-05 11:17:34 -08002370 matches = m.groupdict()
2371 cert = matches["CERT"]
2372 privkey = matches["PRIVKEY"]
2373 name = matches["NAME"]
2374 this_compressed_extension = matches["COMPRESSED"]
2375
2376 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2377 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2378 if cert in SPECIAL_CERT_STRINGS and not privkey:
2379 certmap[name] = cert
2380 elif (cert.endswith(OPTIONS.public_key_suffix) and
2381 privkey.endswith(OPTIONS.private_key_suffix) and
2382 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2383 certmap[name] = cert[:-public_key_suffix_len]
2384 else:
2385 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2386
2387 if not this_compressed_extension:
2388 continue
2389
2390 # Only count the installed files.
2391 filename = name + '.' + this_compressed_extension
2392 if filename not in installed_files:
2393 continue
2394
2395 # Make sure that all the values in the compression map have the same
2396 # extension. We don't support multiple compression methods in the same
2397 # system image.
2398 if compressed_extension:
2399 if this_compressed_extension != compressed_extension:
2400 raise ValueError(
2401 "Multiple compressed extensions: {} vs {}".format(
2402 compressed_extension, this_compressed_extension))
2403 else:
2404 compressed_extension = this_compressed_extension
2405
2406 return (certmap,
2407 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002408
2409
Doug Zongkereef39442009-04-02 12:14:19 -07002410COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002411Global options
2412
2413 -p (--path) <dir>
2414 Prepend <dir>/bin to the list of places to search for binaries run by this
2415 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002416
Doug Zongker05d3dea2009-06-22 11:32:31 -07002417 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002418 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002419
Tao Bao30df8b42018-04-23 15:32:53 -07002420 -x (--extra) <key=value>
2421 Add a key/value pair to the 'extras' dict, which device-specific extension
2422 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002423
Doug Zongkereef39442009-04-02 12:14:19 -07002424 -v (--verbose)
2425 Show command lines being executed.
2426
2427 -h (--help)
2428 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002429
2430 --logfile <file>
2431 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002432"""
2433
Kelvin Zhang0876c412020-06-23 15:06:58 -04002434
Doug Zongkereef39442009-04-02 12:14:19 -07002435def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002436 print(docstring.rstrip("\n"))
2437 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002438
2439
2440def ParseOptions(argv,
2441 docstring,
2442 extra_opts="", extra_long_opts=(),
2443 extra_option_handler=None):
2444 """Parse the options in argv and return any arguments that aren't
2445 flags. docstring is the calling module's docstring, to be displayed
2446 for errors and -h. extra_opts and extra_long_opts are for flags
2447 defined by the caller, which are processed by passing them to
2448 extra_option_handler."""
2449
2450 try:
2451 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002452 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002453 ["help", "verbose", "path=", "signapk_path=",
2454 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002455 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002456 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2457 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002458 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002459 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002460 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002461 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002462 sys.exit(2)
2463
Doug Zongkereef39442009-04-02 12:14:19 -07002464 for o, a in opts:
2465 if o in ("-h", "--help"):
2466 Usage(docstring)
2467 sys.exit()
2468 elif o in ("-v", "--verbose"):
2469 OPTIONS.verbose = True
2470 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002471 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002472 elif o in ("--signapk_path",):
2473 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002474 elif o in ("--signapk_shared_library_path",):
2475 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002476 elif o in ("--extra_signapk_args",):
2477 OPTIONS.extra_signapk_args = shlex.split(a)
2478 elif o in ("--java_path",):
2479 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002480 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002481 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002482 elif o in ("--android_jar_path",):
2483 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002484 elif o in ("--public_key_suffix",):
2485 OPTIONS.public_key_suffix = a
2486 elif o in ("--private_key_suffix",):
2487 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002488 elif o in ("--boot_signer_path",):
2489 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002490 elif o in ("--boot_signer_args",):
2491 OPTIONS.boot_signer_args = shlex.split(a)
2492 elif o in ("--verity_signer_path",):
2493 OPTIONS.verity_signer_path = a
2494 elif o in ("--verity_signer_args",):
2495 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002496 elif o in ("-s", "--device_specific"):
2497 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002498 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002499 key, value = a.split("=", 1)
2500 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002501 elif o in ("--logfile",):
2502 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002503 else:
2504 if extra_option_handler is None or not extra_option_handler(o, a):
2505 assert False, "unknown option \"%s\"" % (o,)
2506
Doug Zongker85448772014-09-09 14:59:20 -07002507 if OPTIONS.search_path:
2508 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2509 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002510
2511 return args
2512
2513
Tao Bao4c851b12016-09-19 13:54:38 -07002514def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002515 """Make a temp file and add it to the list of things to be deleted
2516 when Cleanup() is called. Return the filename."""
2517 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2518 os.close(fd)
2519 OPTIONS.tempfiles.append(fn)
2520 return fn
2521
2522
Tao Bao1c830bf2017-12-25 10:43:47 -08002523def MakeTempDir(prefix='tmp', suffix=''):
2524 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2525
2526 Returns:
2527 The absolute pathname of the new directory.
2528 """
2529 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2530 OPTIONS.tempfiles.append(dir_name)
2531 return dir_name
2532
2533
Doug Zongkereef39442009-04-02 12:14:19 -07002534def Cleanup():
2535 for i in OPTIONS.tempfiles:
2536 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002537 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002538 else:
2539 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002540 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002541
2542
2543class PasswordManager(object):
2544 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002545 self.editor = os.getenv("EDITOR")
2546 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002547
2548 def GetPasswords(self, items):
2549 """Get passwords corresponding to each string in 'items',
2550 returning a dict. (The dict may have keys in addition to the
2551 values in 'items'.)
2552
2553 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2554 user edit that file to add more needed passwords. If no editor is
2555 available, or $ANDROID_PW_FILE isn't define, prompts the user
2556 interactively in the ordinary way.
2557 """
2558
2559 current = self.ReadFile()
2560
2561 first = True
2562 while True:
2563 missing = []
2564 for i in items:
2565 if i not in current or not current[i]:
2566 missing.append(i)
2567 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002568 if not missing:
2569 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002570
2571 for i in missing:
2572 current[i] = ""
2573
2574 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002575 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002576 if sys.version_info[0] >= 3:
2577 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002578 answer = raw_input("try to edit again? [y]> ").strip()
2579 if answer and answer[0] not in 'yY':
2580 raise RuntimeError("key passwords unavailable")
2581 first = False
2582
2583 current = self.UpdateAndReadFile(current)
2584
Kelvin Zhang0876c412020-06-23 15:06:58 -04002585 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002586 """Prompt the user to enter a value (password) for each key in
2587 'current' whose value is fales. Returns a new dict with all the
2588 values.
2589 """
2590 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002591 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002592 if v:
2593 result[k] = v
2594 else:
2595 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002596 result[k] = getpass.getpass(
2597 "Enter password for %s key> " % k).strip()
2598 if result[k]:
2599 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002600 return result
2601
2602 def UpdateAndReadFile(self, current):
2603 if not self.editor or not self.pwfile:
2604 return self.PromptResult(current)
2605
2606 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002607 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002608 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2609 f.write("# (Additional spaces are harmless.)\n\n")
2610
2611 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002612 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002613 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002614 f.write("[[[ %s ]]] %s\n" % (v, k))
2615 if not v and first_line is None:
2616 # position cursor on first line with no password.
2617 first_line = i + 4
2618 f.close()
2619
Tao Bao986ee862018-10-04 15:46:16 -07002620 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002621
2622 return self.ReadFile()
2623
2624 def ReadFile(self):
2625 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002626 if self.pwfile is None:
2627 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002628 try:
2629 f = open(self.pwfile, "r")
2630 for line in f:
2631 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002632 if not line or line[0] == '#':
2633 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002634 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2635 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002636 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002637 else:
2638 result[m.group(2)] = m.group(1)
2639 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002640 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002641 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002642 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002643 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002644
2645
Dan Albert8e0178d2015-01-27 15:53:15 -08002646def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2647 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002648
2649 # http://b/18015246
2650 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2651 # for files larger than 2GiB. We can work around this by adjusting their
2652 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2653 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2654 # it isn't clear to me exactly what circumstances cause this).
2655 # `zipfile.write()` must be used directly to work around this.
2656 #
2657 # This mess can be avoided if we port to python3.
2658 saved_zip64_limit = zipfile.ZIP64_LIMIT
2659 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2660
2661 if compress_type is None:
2662 compress_type = zip_file.compression
2663 if arcname is None:
2664 arcname = filename
2665
2666 saved_stat = os.stat(filename)
2667
2668 try:
2669 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2670 # file to be zipped and reset it when we're done.
2671 os.chmod(filename, perms)
2672
2673 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002674 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2675 # intentional. zip stores datetimes in local time without a time zone
2676 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2677 # in the zip archive.
2678 local_epoch = datetime.datetime.fromtimestamp(0)
2679 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002680 os.utime(filename, (timestamp, timestamp))
2681
2682 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2683 finally:
2684 os.chmod(filename, saved_stat.st_mode)
2685 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2686 zipfile.ZIP64_LIMIT = saved_zip64_limit
2687
2688
Tao Bao58c1b962015-05-20 09:32:18 -07002689def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002690 compress_type=None):
2691 """Wrap zipfile.writestr() function to work around the zip64 limit.
2692
2693 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2694 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2695 when calling crc32(bytes).
2696
2697 But it still works fine to write a shorter string into a large zip file.
2698 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2699 when we know the string won't be too long.
2700 """
2701
2702 saved_zip64_limit = zipfile.ZIP64_LIMIT
2703 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2704
2705 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2706 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002707 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002708 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002709 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002710 else:
Tao Baof3282b42015-04-01 11:21:55 -07002711 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002712 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2713 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2714 # such a case (since
2715 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2716 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2717 # permission bits. We follow the logic in Python 3 to get consistent
2718 # behavior between using the two versions.
2719 if not zinfo.external_attr:
2720 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002721
2722 # If compress_type is given, it overrides the value in zinfo.
2723 if compress_type is not None:
2724 zinfo.compress_type = compress_type
2725
Tao Bao58c1b962015-05-20 09:32:18 -07002726 # If perms is given, it has a priority.
2727 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002728 # If perms doesn't set the file type, mark it as a regular file.
2729 if perms & 0o770000 == 0:
2730 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002731 zinfo.external_attr = perms << 16
2732
Tao Baof3282b42015-04-01 11:21:55 -07002733 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002734 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2735
Dan Albert8b72aef2015-03-23 19:13:21 -07002736 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002737 zipfile.ZIP64_LIMIT = saved_zip64_limit
2738
2739
Tao Bao89d7ab22017-12-14 17:05:33 -08002740def ZipDelete(zip_filename, entries):
2741 """Deletes entries from a ZIP file.
2742
2743 Since deleting entries from a ZIP file is not supported, it shells out to
2744 'zip -d'.
2745
2746 Args:
2747 zip_filename: The name of the ZIP file.
2748 entries: The name of the entry, or the list of names to be deleted.
2749
2750 Raises:
2751 AssertionError: In case of non-zero return from 'zip'.
2752 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002753 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002754 entries = [entries]
2755 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002756 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002757
2758
Tao Baof3282b42015-04-01 11:21:55 -07002759def ZipClose(zip_file):
2760 # http://b/18015246
2761 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2762 # central directory.
2763 saved_zip64_limit = zipfile.ZIP64_LIMIT
2764 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2765
2766 zip_file.close()
2767
2768 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002769
2770
2771class DeviceSpecificParams(object):
2772 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002773
Doug Zongker05d3dea2009-06-22 11:32:31 -07002774 def __init__(self, **kwargs):
2775 """Keyword arguments to the constructor become attributes of this
2776 object, which is passed to all functions in the device-specific
2777 module."""
Tao Bao38884282019-07-10 22:20:56 -07002778 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002779 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002780 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002781
2782 if self.module is None:
2783 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002784 if not path:
2785 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002786 try:
2787 if os.path.isdir(path):
2788 info = imp.find_module("releasetools", [path])
2789 else:
2790 d, f = os.path.split(path)
2791 b, x = os.path.splitext(f)
2792 if x == ".py":
2793 f = b
2794 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002795 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002796 self.module = imp.load_module("device_specific", *info)
2797 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002798 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002799
2800 def _DoCall(self, function_name, *args, **kwargs):
2801 """Call the named function in the device-specific module, passing
2802 the given args and kwargs. The first argument to the call will be
2803 the DeviceSpecific object itself. If there is no module, or the
2804 module does not define the function, return the value of the
2805 'default' kwarg (which itself defaults to None)."""
2806 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002807 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002808 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2809
2810 def FullOTA_Assertions(self):
2811 """Called after emitting the block of assertions at the top of a
2812 full OTA package. Implementations can add whatever additional
2813 assertions they like."""
2814 return self._DoCall("FullOTA_Assertions")
2815
Doug Zongkere5ff5902012-01-17 10:55:37 -08002816 def FullOTA_InstallBegin(self):
2817 """Called at the start of full OTA installation."""
2818 return self._DoCall("FullOTA_InstallBegin")
2819
Yifan Hong10c530d2018-12-27 17:34:18 -08002820 def FullOTA_GetBlockDifferences(self):
2821 """Called during full OTA installation and verification.
2822 Implementation should return a list of BlockDifference objects describing
2823 the update on each additional partitions.
2824 """
2825 return self._DoCall("FullOTA_GetBlockDifferences")
2826
Doug Zongker05d3dea2009-06-22 11:32:31 -07002827 def FullOTA_InstallEnd(self):
2828 """Called at the end of full OTA installation; typically this is
2829 used to install the image for the device's baseband processor."""
2830 return self._DoCall("FullOTA_InstallEnd")
2831
2832 def IncrementalOTA_Assertions(self):
2833 """Called after emitting the block of assertions at the top of an
2834 incremental OTA package. Implementations can add whatever
2835 additional assertions they like."""
2836 return self._DoCall("IncrementalOTA_Assertions")
2837
Doug Zongkere5ff5902012-01-17 10:55:37 -08002838 def IncrementalOTA_VerifyBegin(self):
2839 """Called at the start of the verification phase of incremental
2840 OTA installation; additional checks can be placed here to abort
2841 the script before any changes are made."""
2842 return self._DoCall("IncrementalOTA_VerifyBegin")
2843
Doug Zongker05d3dea2009-06-22 11:32:31 -07002844 def IncrementalOTA_VerifyEnd(self):
2845 """Called at the end of the verification phase of incremental OTA
2846 installation; additional checks can be placed here to abort the
2847 script before any changes are made."""
2848 return self._DoCall("IncrementalOTA_VerifyEnd")
2849
Doug Zongkere5ff5902012-01-17 10:55:37 -08002850 def IncrementalOTA_InstallBegin(self):
2851 """Called at the start of incremental OTA installation (after
2852 verification is complete)."""
2853 return self._DoCall("IncrementalOTA_InstallBegin")
2854
Yifan Hong10c530d2018-12-27 17:34:18 -08002855 def IncrementalOTA_GetBlockDifferences(self):
2856 """Called during incremental OTA installation and verification.
2857 Implementation should return a list of BlockDifference objects describing
2858 the update on each additional partitions.
2859 """
2860 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2861
Doug Zongker05d3dea2009-06-22 11:32:31 -07002862 def IncrementalOTA_InstallEnd(self):
2863 """Called at the end of incremental OTA installation; typically
2864 this is used to install the image for the device's baseband
2865 processor."""
2866 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002867
Tao Bao9bc6bb22015-11-09 16:58:28 -08002868 def VerifyOTA_Assertions(self):
2869 return self._DoCall("VerifyOTA_Assertions")
2870
Tao Bao76def242017-11-21 09:25:31 -08002871
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002872class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002873 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002874 self.name = name
2875 self.data = data
2876 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002877 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002878 self.sha1 = sha1(data).hexdigest()
2879
2880 @classmethod
2881 def FromLocalFile(cls, name, diskname):
2882 f = open(diskname, "rb")
2883 data = f.read()
2884 f.close()
2885 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002886
2887 def WriteToTemp(self):
2888 t = tempfile.NamedTemporaryFile()
2889 t.write(self.data)
2890 t.flush()
2891 return t
2892
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002893 def WriteToDir(self, d):
2894 with open(os.path.join(d, self.name), "wb") as fp:
2895 fp.write(self.data)
2896
Geremy Condra36bd3652014-02-06 19:45:10 -08002897 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002898 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002899
Tao Bao76def242017-11-21 09:25:31 -08002900
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002901DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002902 ".gz": "imgdiff",
2903 ".zip": ["imgdiff", "-z"],
2904 ".jar": ["imgdiff", "-z"],
2905 ".apk": ["imgdiff", "-z"],
2906 ".img": "imgdiff",
2907}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002908
Tao Bao76def242017-11-21 09:25:31 -08002909
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002910class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002911 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002912 self.tf = tf
2913 self.sf = sf
2914 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002915 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002916
2917 def ComputePatch(self):
2918 """Compute the patch (as a string of data) needed to turn sf into
2919 tf. Returns the same tuple as GetPatch()."""
2920
2921 tf = self.tf
2922 sf = self.sf
2923
Doug Zongker24cd2802012-08-14 16:36:15 -07002924 if self.diff_program:
2925 diff_program = self.diff_program
2926 else:
2927 ext = os.path.splitext(tf.name)[1]
2928 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002929
2930 ttemp = tf.WriteToTemp()
2931 stemp = sf.WriteToTemp()
2932
2933 ext = os.path.splitext(tf.name)[1]
2934
2935 try:
2936 ptemp = tempfile.NamedTemporaryFile()
2937 if isinstance(diff_program, list):
2938 cmd = copy.copy(diff_program)
2939 else:
2940 cmd = [diff_program]
2941 cmd.append(stemp.name)
2942 cmd.append(ttemp.name)
2943 cmd.append(ptemp.name)
2944 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002945 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002946
Doug Zongkerf8340082014-08-05 10:39:37 -07002947 def run():
2948 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002949 if e:
2950 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002951 th = threading.Thread(target=run)
2952 th.start()
2953 th.join(timeout=300) # 5 mins
2954 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002955 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002956 p.terminate()
2957 th.join(5)
2958 if th.is_alive():
2959 p.kill()
2960 th.join()
2961
Tianjie Xua2a9f992018-01-05 15:15:54 -08002962 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07002963 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002964 self.patch = None
2965 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002966 diff = ptemp.read()
2967 finally:
2968 ptemp.close()
2969 stemp.close()
2970 ttemp.close()
2971
2972 self.patch = diff
2973 return self.tf, self.sf, self.patch
2974
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002975 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002976 """Returns a tuple of (target_file, source_file, patch_data).
2977
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002978 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002979 computing the patch failed.
2980 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002981 return self.tf, self.sf, self.patch
2982
2983
2984def ComputeDifferences(diffs):
2985 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002986 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002987
2988 # Do the largest files first, to try and reduce the long-pole effect.
2989 by_size = [(i.tf.size, i) for i in diffs]
2990 by_size.sort(reverse=True)
2991 by_size = [i[1] for i in by_size]
2992
2993 lock = threading.Lock()
2994 diff_iter = iter(by_size) # accessed under lock
2995
2996 def worker():
2997 try:
2998 lock.acquire()
2999 for d in diff_iter:
3000 lock.release()
3001 start = time.time()
3002 d.ComputePatch()
3003 dur = time.time() - start
3004 lock.acquire()
3005
3006 tf, sf, patch = d.GetPatch()
3007 if sf.name == tf.name:
3008 name = tf.name
3009 else:
3010 name = "%s (%s)" % (tf.name, sf.name)
3011 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003012 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003013 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003014 logger.info(
3015 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3016 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003017 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003018 except Exception:
3019 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003020 raise
3021
3022 # start worker threads; wait for them all to finish.
3023 threads = [threading.Thread(target=worker)
3024 for i in range(OPTIONS.worker_threads)]
3025 for th in threads:
3026 th.start()
3027 while threads:
3028 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003029
3030
Dan Albert8b72aef2015-03-23 19:13:21 -07003031class BlockDifference(object):
3032 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003033 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003034 self.tgt = tgt
3035 self.src = src
3036 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003037 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003038 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003039
Tao Baodd2a5892015-03-12 12:32:37 -07003040 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003041 version = max(
3042 int(i) for i in
3043 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003044 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003045 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003046
Tianjie Xu41976c72019-07-03 13:57:01 -07003047 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3048 version=self.version,
3049 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003050 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003051 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003052 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003053 self.touched_src_ranges = b.touched_src_ranges
3054 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003055
Yifan Hong10c530d2018-12-27 17:34:18 -08003056 # On devices with dynamic partitions, for new partitions,
3057 # src is None but OPTIONS.source_info_dict is not.
3058 if OPTIONS.source_info_dict is None:
3059 is_dynamic_build = OPTIONS.info_dict.get(
3060 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003061 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003062 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003063 is_dynamic_build = OPTIONS.source_info_dict.get(
3064 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003065 is_dynamic_source = partition in shlex.split(
3066 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003067
Yifan Hongbb2658d2019-01-25 12:30:58 -08003068 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003069 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3070
Yifan Hongbb2658d2019-01-25 12:30:58 -08003071 # For dynamic partitions builds, check partition list in both source
3072 # and target build because new partitions may be added, and existing
3073 # partitions may be removed.
3074 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3075
Yifan Hong10c530d2018-12-27 17:34:18 -08003076 if is_dynamic:
3077 self.device = 'map_partition("%s")' % partition
3078 else:
3079 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003080 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3081 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003082 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003083 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3084 OPTIONS.source_info_dict)
3085 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003086
Tao Baod8d14be2016-02-04 14:26:02 -08003087 @property
3088 def required_cache(self):
3089 return self._required_cache
3090
Tao Bao76def242017-11-21 09:25:31 -08003091 def WriteScript(self, script, output_zip, progress=None,
3092 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003093 if not self.src:
3094 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003095 script.Print("Patching %s image unconditionally..." % (self.partition,))
3096 else:
3097 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003098
Dan Albert8b72aef2015-03-23 19:13:21 -07003099 if progress:
3100 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003101 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003102
3103 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003104 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003105
Tao Bao9bc6bb22015-11-09 16:58:28 -08003106 def WriteStrictVerifyScript(self, script):
3107 """Verify all the blocks in the care_map, including clobbered blocks.
3108
3109 This differs from the WriteVerifyScript() function: a) it prints different
3110 error messages; b) it doesn't allow half-way updated images to pass the
3111 verification."""
3112
3113 partition = self.partition
3114 script.Print("Verifying %s..." % (partition,))
3115 ranges = self.tgt.care_map
3116 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003117 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003118 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3119 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003120 self.device, ranges_str,
3121 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003122 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003123 script.AppendExtra("")
3124
Tao Baod522bdc2016-04-12 15:53:16 -07003125 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003126 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003127
3128 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003129 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003130 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003131
3132 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003133 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003134 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003135 ranges = self.touched_src_ranges
3136 expected_sha1 = self.touched_src_sha1
3137 else:
3138 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3139 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003140
3141 # No blocks to be checked, skipping.
3142 if not ranges:
3143 return
3144
Tao Bao5ece99d2015-05-12 11:42:31 -07003145 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003146 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003147 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003148 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3149 '"%s.patch.dat")) then' % (
3150 self.device, ranges_str, expected_sha1,
3151 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003152 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003153 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003154
Tianjie Xufc3422a2015-12-15 11:53:59 -08003155 if self.version >= 4:
3156
3157 # Bug: 21124327
3158 # When generating incrementals for the system and vendor partitions in
3159 # version 4 or newer, explicitly check the first block (which contains
3160 # the superblock) of the partition to see if it's what we expect. If
3161 # this check fails, give an explicit log message about the partition
3162 # having been remounted R/W (the most likely explanation).
3163 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003164 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003165
3166 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003167 if partition == "system":
3168 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3169 else:
3170 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003171 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003172 'ifelse (block_image_recover({device}, "{ranges}") && '
3173 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003174 'package_extract_file("{partition}.transfer.list"), '
3175 '"{partition}.new.dat", "{partition}.patch.dat"), '
3176 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003177 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003178 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003179 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003180
Tao Baodd2a5892015-03-12 12:32:37 -07003181 # Abort the OTA update. Note that the incremental OTA cannot be applied
3182 # even if it may match the checksum of the target partition.
3183 # a) If version < 3, operations like move and erase will make changes
3184 # unconditionally and damage the partition.
3185 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003186 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003187 if partition == "system":
3188 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3189 else:
3190 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3191 script.AppendExtra((
3192 'abort("E%d: %s partition has unexpected contents");\n'
3193 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003194
Yifan Hong10c530d2018-12-27 17:34:18 -08003195 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003196 partition = self.partition
3197 script.Print('Verifying the updated %s image...' % (partition,))
3198 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3199 ranges = self.tgt.care_map
3200 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003201 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003202 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003203 self.device, ranges_str,
3204 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003205
3206 # Bug: 20881595
3207 # Verify that extended blocks are really zeroed out.
3208 if self.tgt.extended:
3209 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003210 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003211 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003212 self.device, ranges_str,
3213 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003214 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003215 if partition == "system":
3216 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3217 else:
3218 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003219 script.AppendExtra(
3220 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003221 ' abort("E%d: %s partition has unexpected non-zero contents after '
3222 'OTA update");\n'
3223 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003224 else:
3225 script.Print('Verified the updated %s image.' % (partition,))
3226
Tianjie Xu209db462016-05-24 17:34:52 -07003227 if partition == "system":
3228 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3229 else:
3230 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3231
Tao Bao5fcaaef2015-06-01 13:40:49 -07003232 script.AppendExtra(
3233 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003234 ' abort("E%d: %s partition has unexpected contents after OTA '
3235 'update");\n'
3236 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003237
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003238 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003239 ZipWrite(output_zip,
3240 '{}.transfer.list'.format(self.path),
3241 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003242
Tao Bao76def242017-11-21 09:25:31 -08003243 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3244 # its size. Quailty 9 almost triples the compression time but doesn't
3245 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003246 # zip | brotli(quality 6) | brotli(quality 9)
3247 # compressed_size: 942M | 869M (~8% reduced) | 854M
3248 # compression_time: 75s | 265s | 719s
3249 # decompression_time: 15s | 25s | 25s
3250
3251 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003252 brotli_cmd = ['brotli', '--quality=6',
3253 '--output={}.new.dat.br'.format(self.path),
3254 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003255 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003256 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003257
3258 new_data_name = '{}.new.dat.br'.format(self.partition)
3259 ZipWrite(output_zip,
3260 '{}.new.dat.br'.format(self.path),
3261 new_data_name,
3262 compress_type=zipfile.ZIP_STORED)
3263 else:
3264 new_data_name = '{}.new.dat'.format(self.partition)
3265 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3266
Dan Albert8e0178d2015-01-27 15:53:15 -08003267 ZipWrite(output_zip,
3268 '{}.patch.dat'.format(self.path),
3269 '{}.patch.dat'.format(self.partition),
3270 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003271
Tianjie Xu209db462016-05-24 17:34:52 -07003272 if self.partition == "system":
3273 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3274 else:
3275 code = ErrorCode.VENDOR_UPDATE_FAILURE
3276
Yifan Hong10c530d2018-12-27 17:34:18 -08003277 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003278 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003279 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003280 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003281 device=self.device, partition=self.partition,
3282 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003283 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003284
Kelvin Zhang0876c412020-06-23 15:06:58 -04003285 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003286 data = source.ReadRangeSet(ranges)
3287 ctx = sha1()
3288
3289 for p in data:
3290 ctx.update(p)
3291
3292 return ctx.hexdigest()
3293
Kelvin Zhang0876c412020-06-23 15:06:58 -04003294 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003295 """Return the hash value for all zero blocks."""
3296 zero_block = '\x00' * 4096
3297 ctx = sha1()
3298 for _ in range(num_blocks):
3299 ctx.update(zero_block)
3300
3301 return ctx.hexdigest()
3302
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003303
Tianjie Xu41976c72019-07-03 13:57:01 -07003304# Expose these two classes to support vendor-specific scripts
3305DataImage = images.DataImage
3306EmptyImage = images.EmptyImage
3307
Tao Bao76def242017-11-21 09:25:31 -08003308
Doug Zongker96a57e72010-09-26 14:57:41 -07003309# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003310PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003311 "ext4": "EMMC",
3312 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003313 "f2fs": "EMMC",
3314 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003315}
Doug Zongker96a57e72010-09-26 14:57:41 -07003316
Kelvin Zhang0876c412020-06-23 15:06:58 -04003317
Yifan Hongbdb32012020-05-07 12:38:53 -07003318def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3319 """
3320 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3321 backwards compatibility. It aborts if the fstab entry has slotselect option
3322 (unless check_no_slot is explicitly set to False).
3323 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003324 fstab = info["fstab"]
3325 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003326 if check_no_slot:
3327 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003328 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003329 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3330 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003331 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003332
3333
Yifan Hongbdb32012020-05-07 12:38:53 -07003334def GetTypeAndDeviceExpr(mount_point, info):
3335 """
3336 Return the filesystem of the partition, and an edify expression that evaluates
3337 to the device at runtime.
3338 """
3339 fstab = info["fstab"]
3340 if fstab:
3341 p = fstab[mount_point]
3342 device_expr = '"%s"' % fstab[mount_point].device
3343 if p.slotselect:
3344 device_expr = 'add_slot_suffix(%s)' % device_expr
3345 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003346 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003347
3348
3349def GetEntryForDevice(fstab, device):
3350 """
3351 Returns:
3352 The first entry in fstab whose device is the given value.
3353 """
3354 if not fstab:
3355 return None
3356 for mount_point in fstab:
3357 if fstab[mount_point].device == device:
3358 return fstab[mount_point]
3359 return None
3360
Kelvin Zhang0876c412020-06-23 15:06:58 -04003361
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003362def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003363 """Parses and converts a PEM-encoded certificate into DER-encoded.
3364
3365 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3366
3367 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003368 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003369 """
3370 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003371 save = False
3372 for line in data.split("\n"):
3373 if "--END CERTIFICATE--" in line:
3374 break
3375 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003376 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003377 if "--BEGIN CERTIFICATE--" in line:
3378 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003379 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003380 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003381
Tao Bao04e1f012018-02-04 12:13:35 -08003382
3383def ExtractPublicKey(cert):
3384 """Extracts the public key (PEM-encoded) from the given certificate file.
3385
3386 Args:
3387 cert: The certificate filename.
3388
3389 Returns:
3390 The public key string.
3391
3392 Raises:
3393 AssertionError: On non-zero return from 'openssl'.
3394 """
3395 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3396 # While openssl 1.1 writes the key into the given filename followed by '-out',
3397 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3398 # stdout instead.
3399 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3400 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3401 pubkey, stderrdata = proc.communicate()
3402 assert proc.returncode == 0, \
3403 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3404 return pubkey
3405
3406
Tao Bao1ac886e2019-06-26 11:58:22 -07003407def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003408 """Extracts the AVB public key from the given public or private key.
3409
3410 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003411 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003412 key: The input key file, which should be PEM-encoded public or private key.
3413
3414 Returns:
3415 The path to the extracted AVB public key file.
3416 """
3417 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3418 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003419 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003420 return output
3421
3422
Doug Zongker412c02f2014-02-13 10:58:24 -08003423def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3424 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003425 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003426
Tao Bao6d5d6232018-03-09 17:04:42 -08003427 Most of the space in the boot and recovery images is just the kernel, which is
3428 identical for the two, so the resulting patch should be efficient. Add it to
3429 the output zip, along with a shell script that is run from init.rc on first
3430 boot to actually do the patching and install the new recovery image.
3431
3432 Args:
3433 input_dir: The top-level input directory of the target-files.zip.
3434 output_sink: The callback function that writes the result.
3435 recovery_img: File object for the recovery image.
3436 boot_img: File objects for the boot image.
3437 info_dict: A dict returned by common.LoadInfoDict() on the input
3438 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003439 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003440 if info_dict is None:
3441 info_dict = OPTIONS.info_dict
3442
Tao Bao6d5d6232018-03-09 17:04:42 -08003443 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003444 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3445
3446 if board_uses_vendorimage:
3447 # In this case, the output sink is rooted at VENDOR
3448 recovery_img_path = "etc/recovery.img"
3449 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3450 sh_dir = "bin"
3451 else:
3452 # In this case the output sink is rooted at SYSTEM
3453 recovery_img_path = "vendor/etc/recovery.img"
3454 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3455 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003456
Tao Baof2cffbd2015-07-22 12:33:18 -07003457 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003458 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003459
3460 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003461 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003462 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003463 # With system-root-image, boot and recovery images will have mismatching
3464 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3465 # to handle such a case.
3466 if system_root_image:
3467 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003468 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003469 assert not os.path.exists(path)
3470 else:
3471 diff_program = ["imgdiff"]
3472 if os.path.exists(path):
3473 diff_program.append("-b")
3474 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003475 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003476 else:
3477 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003478
3479 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3480 _, _, patch = d.ComputePatch()
3481 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003482
Dan Albertebb19aa2015-03-27 19:11:53 -07003483 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003484 # The following GetTypeAndDevice()s need to use the path in the target
3485 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003486 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3487 check_no_slot=False)
3488 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3489 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003490 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003491 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003492
Tao Baof2cffbd2015-07-22 12:33:18 -07003493 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003494
3495 # Note that we use /vendor to refer to the recovery resources. This will
3496 # work for a separate vendor partition mounted at /vendor or a
3497 # /system/vendor subdirectory on the system partition, for which init will
3498 # create a symlink from /vendor to /system/vendor.
3499
3500 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003501if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3502 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003503 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003504 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3505 log -t recovery "Installing new recovery image: succeeded" || \\
3506 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003507else
3508 log -t recovery "Recovery image already installed"
3509fi
3510""" % {'type': recovery_type,
3511 'device': recovery_device,
3512 'sha1': recovery_img.sha1,
3513 'size': recovery_img.size}
3514 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003515 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003516if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3517 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003518 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003519 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3520 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3521 log -t recovery "Installing new recovery image: succeeded" || \\
3522 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003523else
3524 log -t recovery "Recovery image already installed"
3525fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003526""" % {'boot_size': boot_img.size,
3527 'boot_sha1': boot_img.sha1,
3528 'recovery_size': recovery_img.size,
3529 'recovery_sha1': recovery_img.sha1,
3530 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003531 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003532 'recovery_type': recovery_type,
3533 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003534 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003535
Bill Peckhame868aec2019-09-17 17:06:47 -07003536 # The install script location moved from /system/etc to /system/bin in the L
3537 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3538 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003539
Tao Bao32fcdab2018-10-12 10:30:39 -07003540 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003541
Tao Baoda30cfa2017-12-01 16:19:46 -08003542 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003543
3544
3545class DynamicPartitionUpdate(object):
3546 def __init__(self, src_group=None, tgt_group=None, progress=None,
3547 block_difference=None):
3548 self.src_group = src_group
3549 self.tgt_group = tgt_group
3550 self.progress = progress
3551 self.block_difference = block_difference
3552
3553 @property
3554 def src_size(self):
3555 if not self.block_difference:
3556 return 0
3557 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3558
3559 @property
3560 def tgt_size(self):
3561 if not self.block_difference:
3562 return 0
3563 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3564
3565 @staticmethod
3566 def _GetSparseImageSize(img):
3567 if not img:
3568 return 0
3569 return img.blocksize * img.total_blocks
3570
3571
3572class DynamicGroupUpdate(object):
3573 def __init__(self, src_size=None, tgt_size=None):
3574 # None: group does not exist. 0: no size limits.
3575 self.src_size = src_size
3576 self.tgt_size = tgt_size
3577
3578
3579class DynamicPartitionsDifference(object):
3580 def __init__(self, info_dict, block_diffs, progress_dict=None,
3581 source_info_dict=None):
3582 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003583 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003584
3585 self._remove_all_before_apply = False
3586 if source_info_dict is None:
3587 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003588 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003589
Tao Baof1113e92019-06-18 12:10:14 -07003590 block_diff_dict = collections.OrderedDict(
3591 [(e.partition, e) for e in block_diffs])
3592
Yifan Hong10c530d2018-12-27 17:34:18 -08003593 assert len(block_diff_dict) == len(block_diffs), \
3594 "Duplicated BlockDifference object for {}".format(
3595 [partition for partition, count in
3596 collections.Counter(e.partition for e in block_diffs).items()
3597 if count > 1])
3598
Yifan Hong79997e52019-01-23 16:56:19 -08003599 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003600
3601 for p, block_diff in block_diff_dict.items():
3602 self._partition_updates[p] = DynamicPartitionUpdate()
3603 self._partition_updates[p].block_difference = block_diff
3604
3605 for p, progress in progress_dict.items():
3606 if p in self._partition_updates:
3607 self._partition_updates[p].progress = progress
3608
3609 tgt_groups = shlex.split(info_dict.get(
3610 "super_partition_groups", "").strip())
3611 src_groups = shlex.split(source_info_dict.get(
3612 "super_partition_groups", "").strip())
3613
3614 for g in tgt_groups:
3615 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003616 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003617 assert p in self._partition_updates, \
3618 "{} is in target super_{}_partition_list but no BlockDifference " \
3619 "object is provided.".format(p, g)
3620 self._partition_updates[p].tgt_group = g
3621
3622 for g in src_groups:
3623 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003624 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003625 assert p in self._partition_updates, \
3626 "{} is in source super_{}_partition_list but no BlockDifference " \
3627 "object is provided.".format(p, g)
3628 self._partition_updates[p].src_group = g
3629
Yifan Hong45433e42019-01-18 13:55:25 -08003630 target_dynamic_partitions = set(shlex.split(info_dict.get(
3631 "dynamic_partition_list", "").strip()))
3632 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3633 if u.tgt_size)
3634 assert block_diffs_with_target == target_dynamic_partitions, \
3635 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3636 list(target_dynamic_partitions), list(block_diffs_with_target))
3637
3638 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3639 "dynamic_partition_list", "").strip()))
3640 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3641 if u.src_size)
3642 assert block_diffs_with_source == source_dynamic_partitions, \
3643 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3644 list(source_dynamic_partitions), list(block_diffs_with_source))
3645
Yifan Hong10c530d2018-12-27 17:34:18 -08003646 if self._partition_updates:
3647 logger.info("Updating dynamic partitions %s",
3648 self._partition_updates.keys())
3649
Yifan Hong79997e52019-01-23 16:56:19 -08003650 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003651
3652 for g in tgt_groups:
3653 self._group_updates[g] = DynamicGroupUpdate()
3654 self._group_updates[g].tgt_size = int(info_dict.get(
3655 "super_%s_group_size" % g, "0").strip())
3656
3657 for g in src_groups:
3658 if g not in self._group_updates:
3659 self._group_updates[g] = DynamicGroupUpdate()
3660 self._group_updates[g].src_size = int(source_info_dict.get(
3661 "super_%s_group_size" % g, "0").strip())
3662
3663 self._Compute()
3664
3665 def WriteScript(self, script, output_zip, write_verify_script=False):
3666 script.Comment('--- Start patching dynamic partitions ---')
3667 for p, u in self._partition_updates.items():
3668 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3669 script.Comment('Patch partition %s' % p)
3670 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3671 write_verify_script=False)
3672
3673 op_list_path = MakeTempFile()
3674 with open(op_list_path, 'w') as f:
3675 for line in self._op_list:
3676 f.write('{}\n'.format(line))
3677
3678 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3679
3680 script.Comment('Update dynamic partition metadata')
3681 script.AppendExtra('assert(update_dynamic_partitions('
3682 'package_extract_file("dynamic_partitions_op_list")));')
3683
3684 if write_verify_script:
3685 for p, u in self._partition_updates.items():
3686 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3687 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003688 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003689
3690 for p, u in self._partition_updates.items():
3691 if u.tgt_size and u.src_size <= u.tgt_size:
3692 script.Comment('Patch partition %s' % p)
3693 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3694 write_verify_script=write_verify_script)
3695 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003696 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003697
3698 script.Comment('--- End patching dynamic partitions ---')
3699
3700 def _Compute(self):
3701 self._op_list = list()
3702
3703 def append(line):
3704 self._op_list.append(line)
3705
3706 def comment(line):
3707 self._op_list.append("# %s" % line)
3708
3709 if self._remove_all_before_apply:
3710 comment('Remove all existing dynamic partitions and groups before '
3711 'applying full OTA')
3712 append('remove_all_groups')
3713
3714 for p, u in self._partition_updates.items():
3715 if u.src_group and not u.tgt_group:
3716 append('remove %s' % p)
3717
3718 for p, u in self._partition_updates.items():
3719 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3720 comment('Move partition %s from %s to default' % (p, u.src_group))
3721 append('move %s default' % p)
3722
3723 for p, u in self._partition_updates.items():
3724 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3725 comment('Shrink partition %s from %d to %d' %
3726 (p, u.src_size, u.tgt_size))
3727 append('resize %s %s' % (p, u.tgt_size))
3728
3729 for g, u in self._group_updates.items():
3730 if u.src_size is not None and u.tgt_size is None:
3731 append('remove_group %s' % g)
3732 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003733 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003734 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3735 append('resize_group %s %d' % (g, u.tgt_size))
3736
3737 for g, u in self._group_updates.items():
3738 if u.src_size is None and u.tgt_size is not None:
3739 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3740 append('add_group %s %d' % (g, u.tgt_size))
3741 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003742 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003743 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3744 append('resize_group %s %d' % (g, u.tgt_size))
3745
3746 for p, u in self._partition_updates.items():
3747 if u.tgt_group and not u.src_group:
3748 comment('Add partition %s to group %s' % (p, u.tgt_group))
3749 append('add %s %s' % (p, u.tgt_group))
3750
3751 for p, u in self._partition_updates.items():
3752 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003753 comment('Grow partition %s from %d to %d' %
3754 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003755 append('resize %s %d' % (p, u.tgt_size))
3756
3757 for p, u in self._partition_updates.items():
3758 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3759 comment('Move partition %s from default to %s' %
3760 (p, u.tgt_group))
3761 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003762
3763
jiajia tangf3f842b2021-03-17 21:49:44 +08003764def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003765 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003766 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003767
3768 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003769 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003770
3771 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003772 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003773 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003774 tmp_dir = MakeTempDir('boot_', suffix='.img')
3775 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003776 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3777 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003778 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3779 if not os.path.isfile(ramdisk):
3780 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3781 return None
3782 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003783 if ramdisk_format == RamdiskFormat.LZ4:
3784 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3785 elif ramdisk_format == RamdiskFormat.GZ:
3786 with open(ramdisk, 'rb') as input_stream:
3787 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003788 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3789 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003790 p2.wait()
3791 else:
3792 logger.error('Only support lz4 or minigzip ramdisk format.')
3793 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003794
3795 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3796 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3797 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3798 # the host environment.
3799 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003800 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003801
Yifan Hongc65a0542021-01-07 14:21:01 -08003802 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3803 prop_file = os.path.join(extracted_ramdisk, search_path)
3804 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003805 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003806 logger.warning(
3807 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003808
Yifan Hong7dc51172021-01-12 11:27:39 -08003809 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003810
Yifan Hong85ac5012021-01-07 14:43:46 -08003811 except ExternalError as e:
3812 logger.warning('Unable to get boot image build props: %s', e)
3813 return None
3814
3815
3816def GetBootImageTimestamp(boot_img):
3817 """
3818 Get timestamp from ramdisk within the boot image
3819
3820 Args:
3821 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3822
3823 Return:
3824 An integer that corresponds to the timestamp of the boot image, or None
3825 if file has unknown format. Raise exception if an unexpected error has
3826 occurred.
3827 """
3828 prop_file = GetBootImageBuildProp(boot_img)
3829 if not prop_file:
3830 return None
3831
3832 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3833 if props is None:
3834 return None
3835
3836 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003837 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3838 if timestamp:
3839 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003840 logger.warning(
3841 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003842 return None
3843
3844 except ExternalError as e:
3845 logger.warning('Unable to get boot image timestamp: %s', e)
3846 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003847
3848
3849def GetCareMap(which, imgname):
3850 """Returns the care_map string for the given partition.
3851
3852 Args:
3853 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3854 imgname: The filename of the image.
3855
3856 Returns:
3857 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3858 RangeSet; or None.
3859 """
3860 assert which in PARTITIONS_WITH_CARE_MAP
3861
3862 # which + "_image_size" contains the size that the actual filesystem image
3863 # resides in, which is all that needs to be verified. The additional blocks in
3864 # the image file contain verity metadata, by reading which would trigger
3865 # invalid reads.
3866 image_size = OPTIONS.info_dict.get(which + "_image_size")
3867 if not image_size:
3868 return None
3869
David Anderson9e95a022021-08-31 21:32:45 -07003870 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
3871
Kelvin Zhang27324132021-03-22 15:38:38 -04003872 image_blocks = int(image_size) // 4096 - 1
3873 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3874
3875 # For sparse images, we will only check the blocks that are listed in the care
3876 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07003877 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04003878 simg = sparse_img.SparseImage(imgname)
3879 care_map_ranges = simg.care_map.intersect(
3880 rangelib.RangeSet("0-{}".format(image_blocks)))
3881
3882 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3883 # image.
3884 else:
3885 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3886
3887 return [which, care_map_ranges.to_string_raw()]
3888
3889
3890def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3891 """Generates and adds care_map.pb for a/b partition that has care_map.
3892
3893 Args:
3894 output_file: The output zip file (needs to be already open),
3895 or file path to write care_map.pb.
3896 ab_partitions: The list of A/B partitions.
3897 image_paths: A map from the partition name to the image path.
3898 """
3899 if not output_file:
3900 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3901
3902 care_map_list = []
3903 for partition in ab_partitions:
3904 partition = partition.strip()
3905 if partition not in PARTITIONS_WITH_CARE_MAP:
3906 continue
3907
3908 verity_block_device = "{}_verity_block_device".format(partition)
3909 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3910 if (verity_block_device in OPTIONS.info_dict or
3911 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3912 if partition not in image_paths:
3913 logger.warning('Potential partition with care_map missing from images: %s',
3914 partition)
3915 continue
3916 image_path = image_paths[partition]
3917 if not os.path.exists(image_path):
3918 raise ExternalError('Expected image at path {}'.format(image_path))
3919
3920 care_map = GetCareMap(partition, image_path)
3921 if not care_map:
3922 continue
3923 care_map_list += care_map
3924
3925 # adds fingerprint field to the care_map
3926 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3927 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3928 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3929 "ro.{}.build.thumbprint".format(partition)]
3930
3931 present_props = [x for x in prop_name_list if
3932 partition_props and partition_props.GetProp(x)]
3933 if not present_props:
3934 logger.warning(
3935 "fingerprint is not present for partition %s", partition)
3936 property_id, fingerprint = "unknown", "unknown"
3937 else:
3938 property_id = present_props[0]
3939 fingerprint = partition_props.GetProp(property_id)
3940 care_map_list += [property_id, fingerprint]
3941
3942 if not care_map_list:
3943 return
3944
3945 # Converts the list into proto buf message by calling care_map_generator; and
3946 # writes the result to a temp file.
3947 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3948 suffix=".txt")
3949 with open(temp_care_map_text, 'w') as text_file:
3950 text_file.write('\n'.join(care_map_list))
3951
3952 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3953 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3954 RunAndCheckOutput(care_map_gen_cmd)
3955
3956 if not isinstance(output_file, zipfile.ZipFile):
3957 shutil.copy(temp_care_map, output_file)
3958 return
3959 # output_file is a zip file
3960 care_map_path = "META/care_map.pb"
3961 if care_map_path in output_file.namelist():
3962 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3963 # replace_updated_files_list used by add_img_to_target_files
3964 if not OPTIONS.replace_updated_files_list:
3965 OPTIONS.replace_updated_files_list = []
3966 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3967 OPTIONS.replace_updated_files_list.append(care_map_path)
3968 else:
3969 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07003970
3971
3972def IsSparseImage(filepath):
3973 with open(filepath, 'rb') as fp:
3974 # Magic for android sparse image format
3975 # https://source.android.com/devices/bootloader/images
3976 return fp.read(4) == b'\x3A\xFF\x26\xED'