blob: 64ac95aaffef0ae2bcf2fe8e2d7e6cc4fa41589b [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.
Andrew Sculle077cf72021-02-18 10:27:29 +0000114AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
115 '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
Yifan Hong10482a22021-01-07 14:38:41 -0800133PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['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
938 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400939 data = PartitionBuildProps._ReadBootPropFile(
940 input_file, 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
jiajia tangf3f842b2021-03-17 21:49:44 +0800949 def _ReadBootPropFile(input_file, 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 """
954 try:
955 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
956 except KeyError:
957 logger.warning('Failed to read IMAGES/boot.img')
958 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800959 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800960 if prop_file is None:
961 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500962 with open(prop_file, "r") as f:
963 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800964
965 @staticmethod
966 def _ReadPartitionPropFile(input_file, name):
967 """
968 Read build.prop for name from input_file.
969 Return empty string if not found.
970 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000971 data = ''
972 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
973 '{}/build.prop'.format(name.upper())]:
974 try:
975 data = ReadFromInputFile(input_file, prop_file)
976 break
977 except KeyError:
978 logger.warning('Failed to read %s', prop_file)
Kelvin Zhang4fc3aa02021-11-16 18:58:58 -0800979 if data == '':
980 logger.warning("Failed to read build.prop for partition {}".format(name))
Yifan Hong10482a22021-01-07 14:38:41 -0800981 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000982
Yifan Hong125d0b62020-09-24 17:07:03 -0700983 @staticmethod
984 def FromBuildPropFile(name, build_prop_file):
985 """Constructs an instance from a build prop file."""
986
987 props = PartitionBuildProps("unknown", name)
988 with open(build_prop_file) as f:
989 props._LoadBuildProp(f.read())
990 return props
991
Tianjie Xu9afb2212020-05-10 21:48:15 +0000992 def _LoadBuildProp(self, data):
993 for line in data.split('\n'):
994 line = line.strip()
995 if not line or line.startswith("#"):
996 continue
997 if line.startswith("import"):
998 overrides = self._ImportParser(line)
999 duplicates = self.prop_overrides.intersection(overrides.keys())
1000 if duplicates:
1001 raise ValueError('prop {} is overridden multiple times'.format(
1002 ','.join(duplicates)))
1003 self.prop_overrides = self.prop_overrides.union(overrides.keys())
1004 self.build_props.update(overrides)
1005 elif "=" in line:
1006 name, value = line.split("=", 1)
1007 if name in self.prop_overrides:
1008 raise ValueError('prop {} is set again after overridden by import '
1009 'statement'.format(name))
1010 self.build_props[name] = value
1011
1012 def _ImportParser(self, line):
1013 """Parses the build prop in a given import statement."""
1014
1015 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001016 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001017 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001018
1019 if len(tokens) == 3:
1020 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1021 return {}
1022
Tianjie Xu9afb2212020-05-10 21:48:15 +00001023 import_path = tokens[1]
1024 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1025 raise ValueError('Unrecognized import path {}'.format(line))
1026
1027 # We only recognize a subset of import statement that the init process
1028 # supports. And we can loose the restriction based on how the dynamic
1029 # fingerprint is used in practice. The placeholder format should be
1030 # ${placeholder}, and its value should be provided by the caller through
1031 # the placeholder_values.
1032 for prop, value in self.placeholder_values.items():
1033 prop_place_holder = '${{{}}}'.format(prop)
1034 if prop_place_holder in import_path:
1035 import_path = import_path.replace(prop_place_holder, value)
1036 if '$' in import_path:
1037 logger.info('Unresolved place holder in import path %s', import_path)
1038 return {}
1039
1040 import_path = import_path.replace('/{}'.format(self.partition),
1041 self.partition.upper())
1042 logger.info('Parsing build props override from %s', import_path)
1043
1044 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1045 d = LoadDictionaryFromLines(lines)
1046 return {key: val for key, val in d.items()
1047 if key in self.props_allow_override}
1048
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001049 def GetProp(self, prop):
1050 return self.build_props.get(prop)
1051
1052
Tianjie Xucfa86222016-03-07 16:31:19 -08001053def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1054 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001055 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001056 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001057 self.mount_point = mount_point
1058 self.fs_type = fs_type
1059 self.device = device
1060 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001061 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001062 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001063
1064 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001065 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001066 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001067 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001068 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001069
Tao Baod1de6f32017-03-01 16:38:48 -08001070 assert fstab_version == 2
1071
1072 d = {}
1073 for line in data.split("\n"):
1074 line = line.strip()
1075 if not line or line.startswith("#"):
1076 continue
1077
1078 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1079 pieces = line.split()
1080 if len(pieces) != 5:
1081 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1082
1083 # Ignore entries that are managed by vold.
1084 options = pieces[4]
1085 if "voldmanaged=" in options:
1086 continue
1087
1088 # It's a good line, parse it.
1089 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001090 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001091 options = options.split(",")
1092 for i in options:
1093 if i.startswith("length="):
1094 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001095 elif i == "slotselect":
1096 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001097 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001098 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001099 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001100
Tao Baod1de6f32017-03-01 16:38:48 -08001101 mount_flags = pieces[3]
1102 # Honor the SELinux context if present.
1103 context = None
1104 for i in mount_flags.split(","):
1105 if i.startswith("context="):
1106 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001107
Tao Baod1de6f32017-03-01 16:38:48 -08001108 mount_point = pieces[1]
1109 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001110 device=pieces[0], length=length, context=context,
1111 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001112
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001113 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001114 # system. Other areas assume system is always at "/system" so point /system
1115 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001116 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001117 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001118 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001119 return d
1120
1121
Tao Bao765668f2019-10-04 22:03:00 -07001122def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1123 """Finds the path to recovery fstab and loads its contents."""
1124 # recovery fstab is only meaningful when installing an update via recovery
1125 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001126 if info_dict.get('ab_update') == 'true' and \
1127 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001128 return None
1129
1130 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1131 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1132 # cases, since it may load the info_dict from an old build (e.g. when
1133 # generating incremental OTAs from that build).
1134 system_root_image = info_dict.get('system_root_image') == 'true'
1135 if info_dict.get('no_recovery') != 'true':
1136 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1137 if isinstance(input_file, zipfile.ZipFile):
1138 if recovery_fstab_path not in input_file.namelist():
1139 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1140 else:
1141 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1142 if not os.path.exists(path):
1143 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1144 return LoadRecoveryFSTab(
1145 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1146 system_root_image)
1147
1148 if info_dict.get('recovery_as_boot') == 'true':
1149 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1150 if isinstance(input_file, zipfile.ZipFile):
1151 if recovery_fstab_path not in input_file.namelist():
1152 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1153 else:
1154 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1155 if not os.path.exists(path):
1156 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1157 return LoadRecoveryFSTab(
1158 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1159 system_root_image)
1160
1161 return None
1162
1163
Doug Zongker37974732010-09-16 17:44:38 -07001164def DumpInfoDict(d):
1165 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001166 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001167
Dan Albert8b72aef2015-03-23 19:13:21 -07001168
Daniel Norman55417142019-11-25 16:04:36 -08001169def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001170 """Merges dynamic partition info variables.
1171
1172 Args:
1173 framework_dict: The dictionary of dynamic partition info variables from the
1174 partial framework target files.
1175 vendor_dict: The dictionary of dynamic partition info variables from the
1176 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001177
1178 Returns:
1179 The merged dynamic partition info dictionary.
1180 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001181
1182 def uniq_concat(a, b):
1183 combined = set(a.split(" "))
1184 combined.update(set(b.split(" ")))
1185 combined = [item.strip() for item in combined if item.strip()]
1186 return " ".join(sorted(combined))
1187
1188 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001189 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001190 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1191
1192 merged_dict = {"use_dynamic_partitions": "true"}
1193
1194 merged_dict["dynamic_partition_list"] = uniq_concat(
1195 framework_dict.get("dynamic_partition_list", ""),
1196 vendor_dict.get("dynamic_partition_list", ""))
1197
1198 # Super block devices are defined by the vendor dict.
1199 if "super_block_devices" in vendor_dict:
1200 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1201 for block_device in merged_dict["super_block_devices"].split(" "):
1202 key = "super_%s_device_size" % block_device
1203 if key not in vendor_dict:
1204 raise ValueError("Vendor dict does not contain required key %s." % key)
1205 merged_dict[key] = vendor_dict[key]
1206
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001207 # Partition groups and group sizes are defined by the vendor dict because
1208 # these values may vary for each board that uses a shared system image.
1209 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001210 for partition_group in merged_dict["super_partition_groups"].split(" "):
1211 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001212 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001213 if key not in vendor_dict:
1214 raise ValueError("Vendor dict does not contain required key %s." % key)
1215 merged_dict[key] = vendor_dict[key]
1216
1217 # Set the partition group's partition list using a concatenation of the
1218 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001219 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001220 merged_dict[key] = uniq_concat(
1221 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301222
Daniel Normanb0c75912020-09-24 14:30:21 -07001223 # Various other flags should be copied from the vendor dict, if defined.
1224 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1225 "super_metadata_device", "super_partition_error_limit",
1226 "super_partition_size"):
1227 if key in vendor_dict.keys():
1228 merged_dict[key] = vendor_dict[key]
1229
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001230 return merged_dict
1231
1232
Daniel Norman21c34f72020-11-11 17:25:50 -08001233def PartitionMapFromTargetFiles(target_files_dir):
1234 """Builds a map from partition -> path within an extracted target files directory."""
1235 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1236 possible_subdirs = {
1237 "system": ["SYSTEM"],
1238 "vendor": ["VENDOR", "SYSTEM/vendor"],
1239 "product": ["PRODUCT", "SYSTEM/product"],
1240 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1241 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1242 "vendor_dlkm": [
1243 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1244 ],
1245 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1246 }
1247 partition_map = {}
1248 for partition, subdirs in possible_subdirs.items():
1249 for subdir in subdirs:
1250 if os.path.exists(os.path.join(target_files_dir, subdir)):
1251 partition_map[partition] = subdir
1252 break
1253 return partition_map
1254
1255
Daniel Normand3351562020-10-29 12:33:11 -07001256def SharedUidPartitionViolations(uid_dict, partition_groups):
1257 """Checks for APK sharedUserIds that cross partition group boundaries.
1258
1259 This uses a single or merged build's shareduid_violation_modules.json
1260 output file, as generated by find_shareduid_violation.py or
1261 core/tasks/find-shareduid-violation.mk.
1262
1263 An error is defined as a sharedUserId that is found in a set of partitions
1264 that span more than one partition group.
1265
1266 Args:
1267 uid_dict: A dictionary created by using the standard json module to read a
1268 complete shareduid_violation_modules.json file.
1269 partition_groups: A list of groups, where each group is a list of
1270 partitions.
1271
1272 Returns:
1273 A list of error messages.
1274 """
1275 errors = []
1276 for uid, partitions in uid_dict.items():
1277 found_in_groups = [
1278 group for group in partition_groups
1279 if set(partitions.keys()) & set(group)
1280 ]
1281 if len(found_in_groups) > 1:
1282 errors.append(
1283 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1284 % (uid, ",".join(sorted(partitions.keys()))))
1285 return errors
1286
1287
Daniel Norman21c34f72020-11-11 17:25:50 -08001288def RunHostInitVerifier(product_out, partition_map):
1289 """Runs host_init_verifier on the init rc files within partitions.
1290
1291 host_init_verifier searches the etc/init path within each partition.
1292
1293 Args:
1294 product_out: PRODUCT_OUT directory, containing partition directories.
1295 partition_map: A map of partition name -> relative path within product_out.
1296 """
1297 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1298 cmd = ["host_init_verifier"]
1299 for partition, path in partition_map.items():
1300 if partition not in allowed_partitions:
1301 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1302 partition)
1303 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1304 # Add --property-contexts if the file exists on the partition.
1305 property_contexts = "%s_property_contexts" % (
1306 "plat" if partition == "system" else partition)
1307 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1308 property_contexts)
1309 if os.path.exists(property_contexts_path):
1310 cmd.append("--property-contexts=%s" % property_contexts_path)
1311 # Add the passwd file if the file exists on the partition.
1312 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1313 if os.path.exists(passwd_path):
1314 cmd.extend(["-p", passwd_path])
1315 return RunAndCheckOutput(cmd)
1316
1317
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001318def AppendAVBSigningArgs(cmd, partition):
1319 """Append signing arguments for avbtool."""
1320 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1321 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001322 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1323 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1324 if os.path.exists(new_key_path):
1325 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001326 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1327 if key_path and algorithm:
1328 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001329 avb_salt = OPTIONS.info_dict.get("avb_salt")
1330 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001331 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001332 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001333
1334
Tao Bao765668f2019-10-04 22:03:00 -07001335def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001336 """Returns the VBMeta arguments for partition.
1337
1338 It sets up the VBMeta argument by including the partition descriptor from the
1339 given 'image', or by configuring the partition as a chained partition.
1340
1341 Args:
1342 partition: The name of the partition (e.g. "system").
1343 image: The path to the partition image.
1344 info_dict: A dict returned by common.LoadInfoDict(). Will use
1345 OPTIONS.info_dict if None has been given.
1346
1347 Returns:
1348 A list of VBMeta arguments.
1349 """
1350 if info_dict is None:
1351 info_dict = OPTIONS.info_dict
1352
1353 # Check if chain partition is used.
1354 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001355 if not key_path:
1356 return ["--include_descriptors_from_image", image]
1357
1358 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1359 # into vbmeta.img. The recovery image will be configured on an independent
1360 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1361 # See details at
1362 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001363 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001364 return []
1365
1366 # Otherwise chain the partition into vbmeta.
1367 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1368 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001369
1370
Tao Bao02a08592018-07-22 12:40:45 -07001371def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1372 """Constructs and returns the arg to build or verify a chained partition.
1373
1374 Args:
1375 partition: The partition name.
1376 info_dict: The info dict to look up the key info and rollback index
1377 location.
1378 key: The key to be used for building or verifying the partition. Defaults to
1379 the key listed in info_dict.
1380
1381 Returns:
1382 A string of form "partition:rollback_index_location:key" that can be used to
1383 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001384 """
1385 if key is None:
1386 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001387 if key and not os.path.exists(key) and OPTIONS.search_path:
1388 new_key_path = os.path.join(OPTIONS.search_path, key)
1389 if os.path.exists(new_key_path):
1390 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001391 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001392 rollback_index_location = info_dict[
1393 "avb_" + partition + "_rollback_index_location"]
1394 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1395
1396
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001397def AppendGkiSigningArgs(cmd):
1398 """Append GKI signing arguments for mkbootimg."""
1399 # e.g., --gki_signing_key path/to/signing_key
1400 # --gki_signing_algorithm SHA256_RSA4096"
1401
1402 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1403 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1404 if not key_path:
1405 return
1406
1407 if not os.path.exists(key_path) and OPTIONS.search_path:
1408 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1409 if os.path.exists(new_key_path):
1410 key_path = new_key_path
1411
1412 # Checks key_path exists, before appending --gki_signing_* args.
1413 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001414 raise ExternalError(
1415 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001416
1417 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1418 if key_path and algorithm:
1419 cmd.extend(["--gki_signing_key", key_path,
1420 "--gki_signing_algorithm", algorithm])
1421
1422 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1423 if signature_args:
1424 cmd.extend(["--gki_signing_signature_args", signature_args])
1425
1426
Daniel Norman276f0622019-07-26 14:13:51 -07001427def BuildVBMeta(image_path, partitions, name, needed_partitions):
1428 """Creates a VBMeta image.
1429
1430 It generates the requested VBMeta image. The requested image could be for
1431 top-level or chained VBMeta image, which is determined based on the name.
1432
1433 Args:
1434 image_path: The output path for the new VBMeta image.
1435 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001436 values. Only valid partition names are accepted, as partitions listed
1437 in common.AVB_PARTITIONS and custom partitions listed in
1438 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001439 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1440 needed_partitions: Partitions whose descriptors should be included into the
1441 generated VBMeta image.
1442
1443 Raises:
1444 AssertionError: On invalid input args.
1445 """
1446 avbtool = OPTIONS.info_dict["avb_avbtool"]
1447 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1448 AppendAVBSigningArgs(cmd, name)
1449
Hongguang Chenf23364d2020-04-27 18:36:36 -07001450 custom_partitions = OPTIONS.info_dict.get(
1451 "avb_custom_images_partition_list", "").strip().split()
1452
Daniel Norman276f0622019-07-26 14:13:51 -07001453 for partition, path in partitions.items():
1454 if partition not in needed_partitions:
1455 continue
1456 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001457 partition in AVB_VBMETA_PARTITIONS or
1458 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001459 'Unknown partition: {}'.format(partition)
1460 assert os.path.exists(path), \
1461 'Failed to find {} for {}'.format(path, partition)
1462 cmd.extend(GetAvbPartitionArg(partition, path))
1463
1464 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1465 if args and args.strip():
1466 split_args = shlex.split(args)
1467 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001468 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001469 # as a path relative to source tree, which may not be available at the
1470 # same location when running this script (we have the input target_files
1471 # zip only). For such cases, we additionally scan other locations (e.g.
1472 # IMAGES/, RADIO/, etc) before bailing out.
1473 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001474 chained_image = split_args[index + 1]
1475 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001476 continue
1477 found = False
1478 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1479 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001480 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001481 if os.path.exists(alt_path):
1482 split_args[index + 1] = alt_path
1483 found = True
1484 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001485 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001486 cmd.extend(split_args)
1487
1488 RunAndCheckOutput(cmd)
1489
1490
jiajia tang836f76b2021-04-02 14:48:26 +08001491def _MakeRamdisk(sourcedir, fs_config_file=None,
1492 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001493 ramdisk_img = tempfile.NamedTemporaryFile()
1494
1495 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1496 cmd = ["mkbootfs", "-f", fs_config_file,
1497 os.path.join(sourcedir, "RAMDISK")]
1498 else:
1499 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1500 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001501 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001502 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001503 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001504 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001505 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001506 else:
1507 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001508
1509 p2.wait()
1510 p1.wait()
1511 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001512 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001513
1514 return ramdisk_img
1515
1516
Steve Muckle9793cf62020-04-08 18:27:00 -07001517def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001518 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001519 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001520
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001521 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001522 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1523 we are building a two-step special image (i.e. building a recovery image to
1524 be loaded into /boot in two-step OTAs).
1525
1526 Return the image data, or None if sourcedir does not appear to contains files
1527 for building the requested image.
1528 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001529
Yifan Hong63c5ca12020-10-08 11:54:02 -07001530 if info_dict is None:
1531 info_dict = OPTIONS.info_dict
1532
Steve Muckle9793cf62020-04-08 18:27:00 -07001533 # "boot" or "recovery", without extension.
1534 partition_name = os.path.basename(sourcedir).lower()
1535
Yifan Hong63c5ca12020-10-08 11:54:02 -07001536 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001537 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001538 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1539 logger.info("Excluded kernel binary from recovery image.")
1540 else:
1541 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001542 else:
1543 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001544 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001545 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001546 return None
1547
1548 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001549 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001550
Doug Zongkereef39442009-04-02 12:14:19 -07001551 img = tempfile.NamedTemporaryFile()
1552
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001553 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001554 ramdisk_format = _GetRamdiskFormat(info_dict)
1555 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1556 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001557
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001558 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1559 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1560
Yifan Hong63c5ca12020-10-08 11:54:02 -07001561 cmd = [mkbootimg]
1562 if kernel:
1563 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001564
Benoit Fradina45a8682014-07-14 21:00:43 +02001565 fn = os.path.join(sourcedir, "second")
1566 if os.access(fn, os.F_OK):
1567 cmd.append("--second")
1568 cmd.append(fn)
1569
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001570 fn = os.path.join(sourcedir, "dtb")
1571 if os.access(fn, os.F_OK):
1572 cmd.append("--dtb")
1573 cmd.append(fn)
1574
Doug Zongker171f1cd2009-06-15 22:36:37 -07001575 fn = os.path.join(sourcedir, "cmdline")
1576 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001577 cmd.append("--cmdline")
1578 cmd.append(open(fn).read().rstrip("\n"))
1579
1580 fn = os.path.join(sourcedir, "base")
1581 if os.access(fn, os.F_OK):
1582 cmd.append("--base")
1583 cmd.append(open(fn).read().rstrip("\n"))
1584
Ying Wang4de6b5b2010-08-25 14:29:34 -07001585 fn = os.path.join(sourcedir, "pagesize")
1586 if os.access(fn, os.F_OK):
1587 cmd.append("--pagesize")
1588 cmd.append(open(fn).read().rstrip("\n"))
1589
Steve Mucklef84668e2020-03-16 19:13:46 -07001590 if partition_name == "recovery":
1591 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301592 if not args:
1593 # Fall back to "mkbootimg_args" for recovery image
1594 # in case "recovery_mkbootimg_args" is not set.
1595 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001596 else:
1597 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001598 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001599 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001600
Tao Bao76def242017-11-21 09:25:31 -08001601 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001602 if args and args.strip():
1603 cmd.extend(shlex.split(args))
1604
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001605 if has_ramdisk:
1606 cmd.extend(["--ramdisk", ramdisk_img.name])
1607
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001608 AppendGkiSigningArgs(cmd)
1609
Tao Baod95e9fd2015-03-29 23:07:41 -07001610 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001611 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001612 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001613 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001614 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001615 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001616
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001617 if partition_name == "recovery":
1618 if info_dict.get("include_recovery_dtbo") == "true":
1619 fn = os.path.join(sourcedir, "recovery_dtbo")
1620 cmd.extend(["--recovery_dtbo", fn])
1621 if info_dict.get("include_recovery_acpio") == "true":
1622 fn = os.path.join(sourcedir, "recovery_acpio")
1623 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001624
Tao Bao986ee862018-10-04 15:46:16 -07001625 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001626
Tao Bao76def242017-11-21 09:25:31 -08001627 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001628 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001629 # Hard-code the path as "/boot" for two-step special recovery image (which
1630 # will be loaded into /boot during the two-step OTA).
1631 if two_step_image:
1632 path = "/boot"
1633 else:
Tao Baobf70c312017-07-11 17:27:55 -07001634 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001635 cmd = [OPTIONS.boot_signer_path]
1636 cmd.extend(OPTIONS.boot_signer_args)
1637 cmd.extend([path, img.name,
1638 info_dict["verity_key"] + ".pk8",
1639 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001640 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001641
Tao Baod95e9fd2015-03-29 23:07:41 -07001642 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001643 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001644 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001645 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001646 # We have switched from the prebuilt futility binary to using the tool
1647 # (futility-host) built from the source. Override the setting in the old
1648 # TF.zip.
1649 futility = info_dict["futility"]
1650 if futility.startswith("prebuilts/"):
1651 futility = "futility-host"
1652 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001653 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001654 info_dict["vboot_key"] + ".vbprivk",
1655 info_dict["vboot_subkey"] + ".vbprivk",
1656 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001657 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001658 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001659
Tao Baof3282b42015-04-01 11:21:55 -07001660 # Clean up the temp files.
1661 img_unsigned.close()
1662 img_keyblock.close()
1663
David Zeuthen8fecb282017-12-01 16:24:01 -05001664 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001665 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001666 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001667 if partition_name == "recovery":
1668 part_size = info_dict["recovery_size"]
1669 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001670 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001671 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001672 "--partition_size", str(part_size), "--partition_name",
1673 partition_name]
1674 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001675 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001676 if args and args.strip():
1677 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001678 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001679
1680 img.seek(os.SEEK_SET, 0)
1681 data = img.read()
1682
1683 if has_ramdisk:
1684 ramdisk_img.close()
1685 img.close()
1686
1687 return data
1688
1689
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001690def _SignBootableImage(image_path, prebuilt_name, partition_name,
1691 info_dict=None):
1692 """Performs AVB signing for a prebuilt boot.img.
1693
1694 Args:
1695 image_path: The full path of the image, e.g., /path/to/boot.img.
1696 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1697 boot-5.10.img, recovery.img.
1698 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1699 info_dict: The information dict read from misc_info.txt.
1700 """
1701 if info_dict is None:
1702 info_dict = OPTIONS.info_dict
1703
1704 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1705 if info_dict.get("avb_enable") == "true":
1706 avbtool = info_dict["avb_avbtool"]
1707 if partition_name == "recovery":
1708 part_size = info_dict["recovery_size"]
1709 else:
1710 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1711
1712 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1713 "--partition_size", str(part_size), "--partition_name",
1714 partition_name]
1715 AppendAVBSigningArgs(cmd, partition_name)
1716 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1717 if args and args.strip():
1718 cmd.extend(shlex.split(args))
1719 RunAndCheckOutput(cmd)
1720
1721
Doug Zongkerd5131602012-08-02 14:46:42 -07001722def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001723 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001724 """Return a File object with the desired bootable image.
1725
1726 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1727 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1728 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001729
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001730 if info_dict is None:
1731 info_dict = OPTIONS.info_dict
1732
Doug Zongker55d93282011-01-25 17:03:34 -08001733 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1734 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001735 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001736 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001737
1738 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1739 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001740 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001741 return File.FromLocalFile(name, prebuilt_path)
1742
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001743 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1744 if os.path.exists(prebuilt_path):
1745 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1746 signed_img = MakeTempFile()
1747 shutil.copy(prebuilt_path, signed_img)
1748 partition_name = tree_subdir.lower()
1749 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1750 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001751
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001752 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001753
1754 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001755 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1756 # for recovery.
1757 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1758 prebuilt_name != "boot.img" or
1759 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001760
Doug Zongker6f1d0312014-08-22 08:07:12 -07001761 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001762 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001763 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001764 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001765 if data:
1766 return File(name, data)
1767 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001768
Doug Zongkereef39442009-04-02 12:14:19 -07001769
Steve Mucklee1b10862019-07-10 10:49:37 -07001770def _BuildVendorBootImage(sourcedir, info_dict=None):
1771 """Build a vendor boot image from the specified sourcedir.
1772
1773 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1774 turn them into a vendor boot image.
1775
1776 Return the image data, or None if sourcedir does not appear to contains files
1777 for building the requested image.
1778 """
1779
1780 if info_dict is None:
1781 info_dict = OPTIONS.info_dict
1782
1783 img = tempfile.NamedTemporaryFile()
1784
jiajia tang836f76b2021-04-02 14:48:26 +08001785 ramdisk_format = _GetRamdiskFormat(info_dict)
1786 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001787
1788 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1789 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1790
1791 cmd = [mkbootimg]
1792
1793 fn = os.path.join(sourcedir, "dtb")
1794 if os.access(fn, os.F_OK):
1795 cmd.append("--dtb")
1796 cmd.append(fn)
1797
1798 fn = os.path.join(sourcedir, "vendor_cmdline")
1799 if os.access(fn, os.F_OK):
1800 cmd.append("--vendor_cmdline")
1801 cmd.append(open(fn).read().rstrip("\n"))
1802
1803 fn = os.path.join(sourcedir, "base")
1804 if os.access(fn, os.F_OK):
1805 cmd.append("--base")
1806 cmd.append(open(fn).read().rstrip("\n"))
1807
1808 fn = os.path.join(sourcedir, "pagesize")
1809 if os.access(fn, os.F_OK):
1810 cmd.append("--pagesize")
1811 cmd.append(open(fn).read().rstrip("\n"))
1812
1813 args = info_dict.get("mkbootimg_args")
1814 if args and args.strip():
1815 cmd.extend(shlex.split(args))
1816
1817 args = info_dict.get("mkbootimg_version_args")
1818 if args and args.strip():
1819 cmd.extend(shlex.split(args))
1820
1821 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1822 cmd.extend(["--vendor_boot", img.name])
1823
Devin Moore50509012021-01-13 10:45:04 -08001824 fn = os.path.join(sourcedir, "vendor_bootconfig")
1825 if os.access(fn, os.F_OK):
1826 cmd.append("--vendor_bootconfig")
1827 cmd.append(fn)
1828
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001829 ramdisk_fragment_imgs = []
1830 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1831 if os.access(fn, os.F_OK):
1832 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1833 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001834 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1835 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001836 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001837 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1838 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001839 # Use prebuilt image if found, else create ramdisk from supplied files.
1840 if os.access(fn, os.F_OK):
1841 ramdisk_fragment_pathname = fn
1842 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001843 ramdisk_fragment_root = os.path.join(
1844 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001845 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1846 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001847 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1848 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1849 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1850
Steve Mucklee1b10862019-07-10 10:49:37 -07001851 RunAndCheckOutput(cmd)
1852
1853 # AVB: if enabled, calculate and add hash.
1854 if info_dict.get("avb_enable") == "true":
1855 avbtool = info_dict["avb_avbtool"]
1856 part_size = info_dict["vendor_boot_size"]
1857 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001858 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001859 AppendAVBSigningArgs(cmd, "vendor_boot")
1860 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1861 if args and args.strip():
1862 cmd.extend(shlex.split(args))
1863 RunAndCheckOutput(cmd)
1864
1865 img.seek(os.SEEK_SET, 0)
1866 data = img.read()
1867
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001868 for f in ramdisk_fragment_imgs:
1869 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001870 ramdisk_img.close()
1871 img.close()
1872
1873 return data
1874
1875
1876def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1877 info_dict=None):
1878 """Return a File object with the desired vendor boot image.
1879
1880 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1881 the source files in 'unpack_dir'/'tree_subdir'."""
1882
1883 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1884 if os.path.exists(prebuilt_path):
1885 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1886 return File.FromLocalFile(name, prebuilt_path)
1887
1888 logger.info("building image from target_files %s...", tree_subdir)
1889
1890 if info_dict is None:
1891 info_dict = OPTIONS.info_dict
1892
Kelvin Zhang0876c412020-06-23 15:06:58 -04001893 data = _BuildVendorBootImage(
1894 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001895 if data:
1896 return File(name, data)
1897 return None
1898
1899
Narayan Kamatha07bf042017-08-14 14:49:21 +01001900def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001901 """Gunzips the given gzip compressed file to a given output file."""
1902 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001903 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001904 shutil.copyfileobj(in_file, out_file)
1905
1906
Tao Bao0ff15de2019-03-20 11:26:06 -07001907def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001908 """Unzips the archive to the given directory.
1909
1910 Args:
1911 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001912 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001913 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1914 archvie. Non-matching patterns will be filtered out. If there's no match
1915 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001916 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001917 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001918 if patterns is not None:
1919 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001920 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001921 names = input_zip.namelist()
1922 filtered = [
1923 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1924
1925 # There isn't any matching files. Don't unzip anything.
1926 if not filtered:
1927 return
1928 cmd.extend(filtered)
1929
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001930 RunAndCheckOutput(cmd)
1931
1932
Daniel Norman78554ea2021-09-14 10:29:38 -07001933def UnzipTemp(filename, patterns=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001934 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001935
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001936 Args:
1937 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1938 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1939
Daniel Norman78554ea2021-09-14 10:29:38 -07001940 patterns: Files to unzip from the archive. If omitted, will unzip the entire
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001941 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001942
Tao Bao1c830bf2017-12-25 10:43:47 -08001943 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001944 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001945 """
Doug Zongkereef39442009-04-02 12:14:19 -07001946
Tao Bao1c830bf2017-12-25 10:43:47 -08001947 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001948 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1949 if m:
Daniel Norman78554ea2021-09-14 10:29:38 -07001950 UnzipToDir(m.group(1), tmp, patterns)
1951 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08001952 filename = m.group(1)
1953 else:
Daniel Norman78554ea2021-09-14 10:29:38 -07001954 UnzipToDir(filename, tmp, patterns)
Doug Zongker55d93282011-01-25 17:03:34 -08001955
Tao Baodba59ee2018-01-09 13:21:02 -08001956 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001957
1958
Yifan Hong8a66a712019-04-04 15:37:57 -07001959def GetUserImage(which, tmpdir, input_zip,
1960 info_dict=None,
1961 allow_shared_blocks=None,
1962 hashtree_info_generator=None,
1963 reset_file_map=False):
1964 """Returns an Image object suitable for passing to BlockImageDiff.
1965
1966 This function loads the specified image from the given path. If the specified
1967 image is sparse, it also performs additional processing for OTA purpose. For
1968 example, it always adds block 0 to clobbered blocks list. It also detects
1969 files that cannot be reconstructed from the block list, for whom we should
1970 avoid applying imgdiff.
1971
1972 Args:
1973 which: The partition name.
1974 tmpdir: The directory that contains the prebuilt image and block map file.
1975 input_zip: The target-files ZIP archive.
1976 info_dict: The dict to be looked up for relevant info.
1977 allow_shared_blocks: If image is sparse, whether having shared blocks is
1978 allowed. If none, it is looked up from info_dict.
1979 hashtree_info_generator: If present and image is sparse, generates the
1980 hashtree_info for this sparse image.
1981 reset_file_map: If true and image is sparse, reset file map before returning
1982 the image.
1983 Returns:
1984 A Image object. If it is a sparse image and reset_file_map is False, the
1985 image will have file_map info loaded.
1986 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001987 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001988 info_dict = LoadInfoDict(input_zip)
1989
1990 is_sparse = info_dict.get("extfs_sparse_flag")
David Anderson9e95a022021-08-31 21:32:45 -07001991 if info_dict.get(which + "_disable_sparse"):
1992 is_sparse = False
Yifan Hong8a66a712019-04-04 15:37:57 -07001993
1994 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1995 # shared blocks (i.e. some blocks will show up in multiple files' block
1996 # list). We can only allocate such shared blocks to the first "owner", and
1997 # disable imgdiff for all later occurrences.
1998 if allow_shared_blocks is None:
1999 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2000
2001 if is_sparse:
2002 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2003 hashtree_info_generator)
2004 if reset_file_map:
2005 img.ResetFileMap()
2006 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002007 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002008
2009
2010def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2011 """Returns a Image object suitable for passing to BlockImageDiff.
2012
2013 This function loads the specified non-sparse image from the given path.
2014
2015 Args:
2016 which: The partition name.
2017 tmpdir: The directory that contains the prebuilt image and block map file.
2018 Returns:
2019 A Image object.
2020 """
2021 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2022 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2023
2024 # The image and map files must have been created prior to calling
2025 # ota_from_target_files.py (since LMP).
2026 assert os.path.exists(path) and os.path.exists(mappath)
2027
Tianjie Xu41976c72019-07-03 13:57:01 -07002028 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2029
Yifan Hong8a66a712019-04-04 15:37:57 -07002030
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002031def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2032 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002033 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2034
2035 This function loads the specified sparse image from the given path, and
2036 performs additional processing for OTA purpose. For example, it always adds
2037 block 0 to clobbered blocks list. It also detects files that cannot be
2038 reconstructed from the block list, for whom we should avoid applying imgdiff.
2039
2040 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002041 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002042 tmpdir: The directory that contains the prebuilt image and block map file.
2043 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002044 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002045 hashtree_info_generator: If present, generates the hashtree_info for this
2046 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002047 Returns:
2048 A SparseImage object, with file_map info loaded.
2049 """
Tao Baoc765cca2018-01-31 17:32:40 -08002050 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2051 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2052
2053 # The image and map files must have been created prior to calling
2054 # ota_from_target_files.py (since LMP).
2055 assert os.path.exists(path) and os.path.exists(mappath)
2056
2057 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2058 # it to clobbered_blocks so that it will be written to the target
2059 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2060 clobbered_blocks = "0"
2061
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002062 image = sparse_img.SparseImage(
2063 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2064 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002065
2066 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2067 # if they contain all zeros. We can't reconstruct such a file from its block
2068 # list. Tag such entries accordingly. (Bug: 65213616)
2069 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002070 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002071 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002072 continue
2073
Tom Cherryd14b8952018-08-09 14:26:00 -07002074 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2075 # filename listed in system.map may contain an additional leading slash
2076 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2077 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002078 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002079 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002080 arcname = entry.lstrip('/')
2081 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002082 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002083 else:
2084 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002085
2086 assert arcname in input_zip.namelist(), \
2087 "Failed to find the ZIP entry for {}".format(entry)
2088
Tao Baoc765cca2018-01-31 17:32:40 -08002089 info = input_zip.getinfo(arcname)
2090 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002091
2092 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002093 # image, check the original block list to determine its completeness. Note
2094 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002095 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002096 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002097
Tao Baoc765cca2018-01-31 17:32:40 -08002098 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2099 ranges.extra['incomplete'] = True
2100
2101 return image
2102
2103
Doug Zongkereef39442009-04-02 12:14:19 -07002104def GetKeyPasswords(keylist):
2105 """Given a list of keys, prompt the user to enter passwords for
2106 those which require them. Return a {key: password} dict. password
2107 will be None if the key has no password."""
2108
Doug Zongker8ce7c252009-05-22 13:34:54 -07002109 no_passwords = []
2110 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002111 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002112 devnull = open("/dev/null", "w+b")
Cole Faustb820bcd2021-10-28 13:59:48 -07002113
2114 # sorted() can't compare strings to None, so convert Nones to strings
2115 for k in sorted(keylist, key=lambda x: x if x is not None else ""):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002116 # We don't need a password for things that aren't really keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +09002117 if k in SPECIAL_CERT_STRINGS or k is None:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002118 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002119 continue
2120
T.R. Fullhart37e10522013-03-18 10:31:26 -07002121 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002122 "-inform", "DER", "-nocrypt"],
2123 stdin=devnull.fileno(),
2124 stdout=devnull.fileno(),
2125 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002126 p.communicate()
2127 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002128 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002129 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002130 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002131 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2132 "-inform", "DER", "-passin", "pass:"],
2133 stdin=devnull.fileno(),
2134 stdout=devnull.fileno(),
2135 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002136 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002137 if p.returncode == 0:
2138 # Encrypted key with empty string as password.
2139 key_passwords[k] = ''
2140 elif stderr.startswith('Error decrypting key'):
2141 # Definitely encrypted key.
2142 # It would have said "Error reading key" if it didn't parse correctly.
2143 need_passwords.append(k)
2144 else:
2145 # Potentially, a type of key that openssl doesn't understand.
2146 # We'll let the routines in signapk.jar handle it.
2147 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002148 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002149
T.R. Fullhart37e10522013-03-18 10:31:26 -07002150 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002151 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002152 return key_passwords
2153
2154
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002155def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002156 """Gets the minSdkVersion declared in the APK.
2157
changho.shin0f125362019-07-08 10:59:00 +09002158 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002159 This can be both a decimal number (API Level) or a codename.
2160
2161 Args:
2162 apk_name: The APK filename.
2163
2164 Returns:
2165 The parsed SDK version string.
2166
2167 Raises:
2168 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002169 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002170 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002171 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002172 stderr=subprocess.PIPE)
2173 stdoutdata, stderrdata = proc.communicate()
2174 if proc.returncode != 0:
2175 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002176 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002177 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002178
Tao Baof47bf0f2018-03-21 23:28:51 -07002179 for line in stdoutdata.split("\n"):
2180 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002181 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2182 if m:
2183 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002184 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002185
2186
2187def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002188 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002189
Tao Baof47bf0f2018-03-21 23:28:51 -07002190 If minSdkVersion is set to a codename, it is translated to a number using the
2191 provided map.
2192
2193 Args:
2194 apk_name: The APK filename.
2195
2196 Returns:
2197 The parsed SDK version number.
2198
2199 Raises:
2200 ExternalError: On failing to get the min SDK version number.
2201 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002202 version = GetMinSdkVersion(apk_name)
2203 try:
2204 return int(version)
2205 except ValueError:
2206 # Not a decimal number. Codename?
2207 if version in codename_to_api_level_map:
2208 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002209 raise ExternalError(
2210 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2211 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002212
2213
2214def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002215 codename_to_api_level_map=None, whole_file=False,
2216 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002217 """Sign the input_name zip/jar/apk, producing output_name. Use the
2218 given key and password (the latter may be None if the key does not
2219 have a password.
2220
Doug Zongker951495f2009-08-14 12:44:19 -07002221 If whole_file is true, use the "-w" option to SignApk to embed a
2222 signature that covers the whole file in the archive comment of the
2223 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002224
2225 min_api_level is the API Level (int) of the oldest platform this file may end
2226 up on. If not specified for an APK, the API Level is obtained by interpreting
2227 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2228
2229 codename_to_api_level_map is needed to translate the codename which may be
2230 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002231
2232 Caller may optionally specify extra args to be passed to SignApk, which
2233 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002234 """
Tao Bao76def242017-11-21 09:25:31 -08002235 if codename_to_api_level_map is None:
2236 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002237 if extra_signapk_args is None:
2238 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002239
Alex Klyubin9667b182015-12-10 13:38:50 -08002240 java_library_path = os.path.join(
2241 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2242
Tao Baoe95540e2016-11-08 12:08:53 -08002243 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2244 ["-Djava.library.path=" + java_library_path,
2245 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002246 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002247 if whole_file:
2248 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002249
2250 min_sdk_version = min_api_level
2251 if min_sdk_version is None:
2252 if not whole_file:
2253 min_sdk_version = GetMinSdkVersionInt(
2254 input_name, codename_to_api_level_map)
2255 if min_sdk_version is not None:
2256 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2257
T.R. Fullhart37e10522013-03-18 10:31:26 -07002258 cmd.extend([key + OPTIONS.public_key_suffix,
2259 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002260 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002261
Tao Bao73dd4f42018-10-04 16:25:33 -07002262 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002263 if password is not None:
2264 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002265 stdoutdata, _ = proc.communicate(password)
2266 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002267 raise ExternalError(
2268 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002269 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002270
Doug Zongkereef39442009-04-02 12:14:19 -07002271
Doug Zongker37974732010-09-16 17:44:38 -07002272def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002273 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002274
Tao Bao9dd909e2017-11-14 11:27:32 -08002275 For non-AVB images, raise exception if the data is too big. Print a warning
2276 if the data is nearing the maximum size.
2277
2278 For AVB images, the actual image size should be identical to the limit.
2279
2280 Args:
2281 data: A string that contains all the data for the partition.
2282 target: The partition name. The ".img" suffix is optional.
2283 info_dict: The dict to be looked up for relevant info.
2284 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002285 if target.endswith(".img"):
2286 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002287 mount_point = "/" + target
2288
Ying Wangf8824af2014-06-03 14:07:27 -07002289 fs_type = None
2290 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002291 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002292 if mount_point == "/userdata":
2293 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002294 p = info_dict["fstab"][mount_point]
2295 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002296 device = p.device
2297 if "/" in device:
2298 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002299 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002300 if not fs_type or not limit:
2301 return
Doug Zongkereef39442009-04-02 12:14:19 -07002302
Andrew Boie0f9aec82012-02-14 09:32:52 -08002303 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002304 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2305 # path.
2306 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2307 if size != limit:
2308 raise ExternalError(
2309 "Mismatching image size for %s: expected %d actual %d" % (
2310 target, limit, size))
2311 else:
2312 pct = float(size) * 100.0 / limit
2313 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2314 if pct >= 99.0:
2315 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002316
2317 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002318 logger.warning("\n WARNING: %s\n", msg)
2319 else:
2320 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002321
2322
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002323def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002324 """Parses the APK certs info from a given target-files zip.
2325
2326 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2327 tuple with the following elements: (1) a dictionary that maps packages to
2328 certs (based on the "certificate" and "private_key" attributes in the file;
2329 (2) a string representing the extension of compressed APKs in the target files
2330 (e.g ".gz", ".bro").
2331
2332 Args:
2333 tf_zip: The input target_files ZipFile (already open).
2334
2335 Returns:
2336 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2337 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2338 no compressed APKs.
2339 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002340 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002341 compressed_extension = None
2342
Tao Bao0f990332017-09-08 19:02:54 -07002343 # META/apkcerts.txt contains the info for _all_ the packages known at build
2344 # time. Filter out the ones that are not installed.
2345 installed_files = set()
2346 for name in tf_zip.namelist():
2347 basename = os.path.basename(name)
2348 if basename:
2349 installed_files.add(basename)
2350
Tao Baoda30cfa2017-12-01 16:19:46 -08002351 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002352 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002353 if not line:
2354 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002355 m = re.match(
2356 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002357 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2358 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002359 line)
2360 if not m:
2361 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002362
Tao Bao818ddf52018-01-05 11:17:34 -08002363 matches = m.groupdict()
2364 cert = matches["CERT"]
2365 privkey = matches["PRIVKEY"]
2366 name = matches["NAME"]
2367 this_compressed_extension = matches["COMPRESSED"]
2368
2369 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2370 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2371 if cert in SPECIAL_CERT_STRINGS and not privkey:
2372 certmap[name] = cert
2373 elif (cert.endswith(OPTIONS.public_key_suffix) and
2374 privkey.endswith(OPTIONS.private_key_suffix) and
2375 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2376 certmap[name] = cert[:-public_key_suffix_len]
2377 else:
2378 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2379
2380 if not this_compressed_extension:
2381 continue
2382
2383 # Only count the installed files.
2384 filename = name + '.' + this_compressed_extension
2385 if filename not in installed_files:
2386 continue
2387
2388 # Make sure that all the values in the compression map have the same
2389 # extension. We don't support multiple compression methods in the same
2390 # system image.
2391 if compressed_extension:
2392 if this_compressed_extension != compressed_extension:
2393 raise ValueError(
2394 "Multiple compressed extensions: {} vs {}".format(
2395 compressed_extension, this_compressed_extension))
2396 else:
2397 compressed_extension = this_compressed_extension
2398
2399 return (certmap,
2400 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002401
2402
Doug Zongkereef39442009-04-02 12:14:19 -07002403COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002404Global options
2405
2406 -p (--path) <dir>
2407 Prepend <dir>/bin to the list of places to search for binaries run by this
2408 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002409
Doug Zongker05d3dea2009-06-22 11:32:31 -07002410 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002411 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002412
Tao Bao30df8b42018-04-23 15:32:53 -07002413 -x (--extra) <key=value>
2414 Add a key/value pair to the 'extras' dict, which device-specific extension
2415 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002416
Doug Zongkereef39442009-04-02 12:14:19 -07002417 -v (--verbose)
2418 Show command lines being executed.
2419
2420 -h (--help)
2421 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002422
2423 --logfile <file>
2424 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002425"""
2426
Kelvin Zhang0876c412020-06-23 15:06:58 -04002427
Doug Zongkereef39442009-04-02 12:14:19 -07002428def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002429 print(docstring.rstrip("\n"))
2430 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002431
2432
2433def ParseOptions(argv,
2434 docstring,
2435 extra_opts="", extra_long_opts=(),
2436 extra_option_handler=None):
2437 """Parse the options in argv and return any arguments that aren't
2438 flags. docstring is the calling module's docstring, to be displayed
2439 for errors and -h. extra_opts and extra_long_opts are for flags
2440 defined by the caller, which are processed by passing them to
2441 extra_option_handler."""
2442
2443 try:
2444 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002445 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002446 ["help", "verbose", "path=", "signapk_path=",
2447 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002448 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002449 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2450 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002451 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002452 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002453 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002454 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002455 sys.exit(2)
2456
Doug Zongkereef39442009-04-02 12:14:19 -07002457 for o, a in opts:
2458 if o in ("-h", "--help"):
2459 Usage(docstring)
2460 sys.exit()
2461 elif o in ("-v", "--verbose"):
2462 OPTIONS.verbose = True
2463 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002464 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002465 elif o in ("--signapk_path",):
2466 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002467 elif o in ("--signapk_shared_library_path",):
2468 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002469 elif o in ("--extra_signapk_args",):
2470 OPTIONS.extra_signapk_args = shlex.split(a)
2471 elif o in ("--java_path",):
2472 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002473 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002474 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002475 elif o in ("--android_jar_path",):
2476 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002477 elif o in ("--public_key_suffix",):
2478 OPTIONS.public_key_suffix = a
2479 elif o in ("--private_key_suffix",):
2480 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002481 elif o in ("--boot_signer_path",):
2482 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002483 elif o in ("--boot_signer_args",):
2484 OPTIONS.boot_signer_args = shlex.split(a)
2485 elif o in ("--verity_signer_path",):
2486 OPTIONS.verity_signer_path = a
2487 elif o in ("--verity_signer_args",):
2488 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002489 elif o in ("-s", "--device_specific"):
2490 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002491 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002492 key, value = a.split("=", 1)
2493 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002494 elif o in ("--logfile",):
2495 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002496 else:
2497 if extra_option_handler is None or not extra_option_handler(o, a):
2498 assert False, "unknown option \"%s\"" % (o,)
2499
Doug Zongker85448772014-09-09 14:59:20 -07002500 if OPTIONS.search_path:
2501 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2502 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002503
2504 return args
2505
2506
Tao Bao4c851b12016-09-19 13:54:38 -07002507def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002508 """Make a temp file and add it to the list of things to be deleted
2509 when Cleanup() is called. Return the filename."""
2510 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2511 os.close(fd)
2512 OPTIONS.tempfiles.append(fn)
2513 return fn
2514
2515
Tao Bao1c830bf2017-12-25 10:43:47 -08002516def MakeTempDir(prefix='tmp', suffix=''):
2517 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2518
2519 Returns:
2520 The absolute pathname of the new directory.
2521 """
2522 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2523 OPTIONS.tempfiles.append(dir_name)
2524 return dir_name
2525
2526
Doug Zongkereef39442009-04-02 12:14:19 -07002527def Cleanup():
2528 for i in OPTIONS.tempfiles:
2529 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002530 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002531 else:
2532 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002533 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002534
2535
2536class PasswordManager(object):
2537 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002538 self.editor = os.getenv("EDITOR")
2539 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002540
2541 def GetPasswords(self, items):
2542 """Get passwords corresponding to each string in 'items',
2543 returning a dict. (The dict may have keys in addition to the
2544 values in 'items'.)
2545
2546 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2547 user edit that file to add more needed passwords. If no editor is
2548 available, or $ANDROID_PW_FILE isn't define, prompts the user
2549 interactively in the ordinary way.
2550 """
2551
2552 current = self.ReadFile()
2553
2554 first = True
2555 while True:
2556 missing = []
2557 for i in items:
2558 if i not in current or not current[i]:
2559 missing.append(i)
2560 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002561 if not missing:
2562 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002563
2564 for i in missing:
2565 current[i] = ""
2566
2567 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002568 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002569 if sys.version_info[0] >= 3:
2570 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002571 answer = raw_input("try to edit again? [y]> ").strip()
2572 if answer and answer[0] not in 'yY':
2573 raise RuntimeError("key passwords unavailable")
2574 first = False
2575
2576 current = self.UpdateAndReadFile(current)
2577
Kelvin Zhang0876c412020-06-23 15:06:58 -04002578 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002579 """Prompt the user to enter a value (password) for each key in
2580 'current' whose value is fales. Returns a new dict with all the
2581 values.
2582 """
2583 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002584 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002585 if v:
2586 result[k] = v
2587 else:
2588 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002589 result[k] = getpass.getpass(
2590 "Enter password for %s key> " % k).strip()
2591 if result[k]:
2592 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002593 return result
2594
2595 def UpdateAndReadFile(self, current):
2596 if not self.editor or not self.pwfile:
2597 return self.PromptResult(current)
2598
2599 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002600 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002601 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2602 f.write("# (Additional spaces are harmless.)\n\n")
2603
2604 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002605 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002606 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002607 f.write("[[[ %s ]]] %s\n" % (v, k))
2608 if not v and first_line is None:
2609 # position cursor on first line with no password.
2610 first_line = i + 4
2611 f.close()
2612
Tao Bao986ee862018-10-04 15:46:16 -07002613 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002614
2615 return self.ReadFile()
2616
2617 def ReadFile(self):
2618 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002619 if self.pwfile is None:
2620 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002621 try:
2622 f = open(self.pwfile, "r")
2623 for line in f:
2624 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002625 if not line or line[0] == '#':
2626 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002627 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2628 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002629 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002630 else:
2631 result[m.group(2)] = m.group(1)
2632 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002633 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002634 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002635 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002636 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002637
2638
Dan Albert8e0178d2015-01-27 15:53:15 -08002639def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2640 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002641
2642 # http://b/18015246
2643 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2644 # for files larger than 2GiB. We can work around this by adjusting their
2645 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2646 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2647 # it isn't clear to me exactly what circumstances cause this).
2648 # `zipfile.write()` must be used directly to work around this.
2649 #
2650 # This mess can be avoided if we port to python3.
2651 saved_zip64_limit = zipfile.ZIP64_LIMIT
2652 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2653
2654 if compress_type is None:
2655 compress_type = zip_file.compression
2656 if arcname is None:
2657 arcname = filename
2658
2659 saved_stat = os.stat(filename)
2660
2661 try:
2662 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2663 # file to be zipped and reset it when we're done.
2664 os.chmod(filename, perms)
2665
2666 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002667 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2668 # intentional. zip stores datetimes in local time without a time zone
2669 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2670 # in the zip archive.
2671 local_epoch = datetime.datetime.fromtimestamp(0)
2672 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002673 os.utime(filename, (timestamp, timestamp))
2674
2675 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2676 finally:
2677 os.chmod(filename, saved_stat.st_mode)
2678 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2679 zipfile.ZIP64_LIMIT = saved_zip64_limit
2680
2681
Tao Bao58c1b962015-05-20 09:32:18 -07002682def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002683 compress_type=None):
2684 """Wrap zipfile.writestr() function to work around the zip64 limit.
2685
2686 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2687 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2688 when calling crc32(bytes).
2689
2690 But it still works fine to write a shorter string into a large zip file.
2691 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2692 when we know the string won't be too long.
2693 """
2694
2695 saved_zip64_limit = zipfile.ZIP64_LIMIT
2696 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2697
2698 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2699 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002700 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002701 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002702 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002703 else:
Tao Baof3282b42015-04-01 11:21:55 -07002704 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002705 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2706 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2707 # such a case (since
2708 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2709 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2710 # permission bits. We follow the logic in Python 3 to get consistent
2711 # behavior between using the two versions.
2712 if not zinfo.external_attr:
2713 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002714
2715 # If compress_type is given, it overrides the value in zinfo.
2716 if compress_type is not None:
2717 zinfo.compress_type = compress_type
2718
Tao Bao58c1b962015-05-20 09:32:18 -07002719 # If perms is given, it has a priority.
2720 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002721 # If perms doesn't set the file type, mark it as a regular file.
2722 if perms & 0o770000 == 0:
2723 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002724 zinfo.external_attr = perms << 16
2725
Tao Baof3282b42015-04-01 11:21:55 -07002726 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002727 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2728
Dan Albert8b72aef2015-03-23 19:13:21 -07002729 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002730 zipfile.ZIP64_LIMIT = saved_zip64_limit
2731
2732
Tao Bao89d7ab22017-12-14 17:05:33 -08002733def ZipDelete(zip_filename, entries):
2734 """Deletes entries from a ZIP file.
2735
2736 Since deleting entries from a ZIP file is not supported, it shells out to
2737 'zip -d'.
2738
2739 Args:
2740 zip_filename: The name of the ZIP file.
2741 entries: The name of the entry, or the list of names to be deleted.
2742
2743 Raises:
2744 AssertionError: In case of non-zero return from 'zip'.
2745 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002746 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002747 entries = [entries]
2748 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002749 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002750
2751
Tao Baof3282b42015-04-01 11:21:55 -07002752def ZipClose(zip_file):
2753 # http://b/18015246
2754 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2755 # central directory.
2756 saved_zip64_limit = zipfile.ZIP64_LIMIT
2757 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2758
2759 zip_file.close()
2760
2761 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002762
2763
2764class DeviceSpecificParams(object):
2765 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002766
Doug Zongker05d3dea2009-06-22 11:32:31 -07002767 def __init__(self, **kwargs):
2768 """Keyword arguments to the constructor become attributes of this
2769 object, which is passed to all functions in the device-specific
2770 module."""
Tao Bao38884282019-07-10 22:20:56 -07002771 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002772 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002773 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002774
2775 if self.module is None:
2776 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002777 if not path:
2778 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002779 try:
2780 if os.path.isdir(path):
2781 info = imp.find_module("releasetools", [path])
2782 else:
2783 d, f = os.path.split(path)
2784 b, x = os.path.splitext(f)
2785 if x == ".py":
2786 f = b
2787 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002788 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002789 self.module = imp.load_module("device_specific", *info)
2790 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002791 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002792
2793 def _DoCall(self, function_name, *args, **kwargs):
2794 """Call the named function in the device-specific module, passing
2795 the given args and kwargs. The first argument to the call will be
2796 the DeviceSpecific object itself. If there is no module, or the
2797 module does not define the function, return the value of the
2798 'default' kwarg (which itself defaults to None)."""
2799 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002800 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002801 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2802
2803 def FullOTA_Assertions(self):
2804 """Called after emitting the block of assertions at the top of a
2805 full OTA package. Implementations can add whatever additional
2806 assertions they like."""
2807 return self._DoCall("FullOTA_Assertions")
2808
Doug Zongkere5ff5902012-01-17 10:55:37 -08002809 def FullOTA_InstallBegin(self):
2810 """Called at the start of full OTA installation."""
2811 return self._DoCall("FullOTA_InstallBegin")
2812
Yifan Hong10c530d2018-12-27 17:34:18 -08002813 def FullOTA_GetBlockDifferences(self):
2814 """Called during full OTA installation and verification.
2815 Implementation should return a list of BlockDifference objects describing
2816 the update on each additional partitions.
2817 """
2818 return self._DoCall("FullOTA_GetBlockDifferences")
2819
Doug Zongker05d3dea2009-06-22 11:32:31 -07002820 def FullOTA_InstallEnd(self):
2821 """Called at the end of full OTA installation; typically this is
2822 used to install the image for the device's baseband processor."""
2823 return self._DoCall("FullOTA_InstallEnd")
2824
2825 def IncrementalOTA_Assertions(self):
2826 """Called after emitting the block of assertions at the top of an
2827 incremental OTA package. Implementations can add whatever
2828 additional assertions they like."""
2829 return self._DoCall("IncrementalOTA_Assertions")
2830
Doug Zongkere5ff5902012-01-17 10:55:37 -08002831 def IncrementalOTA_VerifyBegin(self):
2832 """Called at the start of the verification phase of incremental
2833 OTA installation; additional checks can be placed here to abort
2834 the script before any changes are made."""
2835 return self._DoCall("IncrementalOTA_VerifyBegin")
2836
Doug Zongker05d3dea2009-06-22 11:32:31 -07002837 def IncrementalOTA_VerifyEnd(self):
2838 """Called at the end of the verification phase of incremental OTA
2839 installation; additional checks can be placed here to abort the
2840 script before any changes are made."""
2841 return self._DoCall("IncrementalOTA_VerifyEnd")
2842
Doug Zongkere5ff5902012-01-17 10:55:37 -08002843 def IncrementalOTA_InstallBegin(self):
2844 """Called at the start of incremental OTA installation (after
2845 verification is complete)."""
2846 return self._DoCall("IncrementalOTA_InstallBegin")
2847
Yifan Hong10c530d2018-12-27 17:34:18 -08002848 def IncrementalOTA_GetBlockDifferences(self):
2849 """Called during incremental OTA installation and verification.
2850 Implementation should return a list of BlockDifference objects describing
2851 the update on each additional partitions.
2852 """
2853 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2854
Doug Zongker05d3dea2009-06-22 11:32:31 -07002855 def IncrementalOTA_InstallEnd(self):
2856 """Called at the end of incremental OTA installation; typically
2857 this is used to install the image for the device's baseband
2858 processor."""
2859 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002860
Tao Bao9bc6bb22015-11-09 16:58:28 -08002861 def VerifyOTA_Assertions(self):
2862 return self._DoCall("VerifyOTA_Assertions")
2863
Tao Bao76def242017-11-21 09:25:31 -08002864
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002865class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002866 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002867 self.name = name
2868 self.data = data
2869 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002870 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002871 self.sha1 = sha1(data).hexdigest()
2872
2873 @classmethod
2874 def FromLocalFile(cls, name, diskname):
2875 f = open(diskname, "rb")
2876 data = f.read()
2877 f.close()
2878 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002879
2880 def WriteToTemp(self):
2881 t = tempfile.NamedTemporaryFile()
2882 t.write(self.data)
2883 t.flush()
2884 return t
2885
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002886 def WriteToDir(self, d):
2887 with open(os.path.join(d, self.name), "wb") as fp:
2888 fp.write(self.data)
2889
Geremy Condra36bd3652014-02-06 19:45:10 -08002890 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002891 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002892
Tao Bao76def242017-11-21 09:25:31 -08002893
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002894DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002895 ".gz": "imgdiff",
2896 ".zip": ["imgdiff", "-z"],
2897 ".jar": ["imgdiff", "-z"],
2898 ".apk": ["imgdiff", "-z"],
2899 ".img": "imgdiff",
2900}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002901
Tao Bao76def242017-11-21 09:25:31 -08002902
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002903class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002904 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002905 self.tf = tf
2906 self.sf = sf
2907 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002908 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002909
2910 def ComputePatch(self):
2911 """Compute the patch (as a string of data) needed to turn sf into
2912 tf. Returns the same tuple as GetPatch()."""
2913
2914 tf = self.tf
2915 sf = self.sf
2916
Doug Zongker24cd2802012-08-14 16:36:15 -07002917 if self.diff_program:
2918 diff_program = self.diff_program
2919 else:
2920 ext = os.path.splitext(tf.name)[1]
2921 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002922
2923 ttemp = tf.WriteToTemp()
2924 stemp = sf.WriteToTemp()
2925
2926 ext = os.path.splitext(tf.name)[1]
2927
2928 try:
2929 ptemp = tempfile.NamedTemporaryFile()
2930 if isinstance(diff_program, list):
2931 cmd = copy.copy(diff_program)
2932 else:
2933 cmd = [diff_program]
2934 cmd.append(stemp.name)
2935 cmd.append(ttemp.name)
2936 cmd.append(ptemp.name)
2937 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002938 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002939
Doug Zongkerf8340082014-08-05 10:39:37 -07002940 def run():
2941 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002942 if e:
2943 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002944 th = threading.Thread(target=run)
2945 th.start()
2946 th.join(timeout=300) # 5 mins
2947 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002948 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002949 p.terminate()
2950 th.join(5)
2951 if th.is_alive():
2952 p.kill()
2953 th.join()
2954
Tianjie Xua2a9f992018-01-05 15:15:54 -08002955 if p.returncode != 0:
Yifan Honga4140d22021-08-04 18:09:03 -07002956 logger.warning("Failure running %s:\n%s\n", cmd, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002957 self.patch = None
2958 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002959 diff = ptemp.read()
2960 finally:
2961 ptemp.close()
2962 stemp.close()
2963 ttemp.close()
2964
2965 self.patch = diff
2966 return self.tf, self.sf, self.patch
2967
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002968 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002969 """Returns a tuple of (target_file, source_file, patch_data).
2970
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002971 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002972 computing the patch failed.
2973 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002974 return self.tf, self.sf, self.patch
2975
2976
2977def ComputeDifferences(diffs):
2978 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002979 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002980
2981 # Do the largest files first, to try and reduce the long-pole effect.
2982 by_size = [(i.tf.size, i) for i in diffs]
2983 by_size.sort(reverse=True)
2984 by_size = [i[1] for i in by_size]
2985
2986 lock = threading.Lock()
2987 diff_iter = iter(by_size) # accessed under lock
2988
2989 def worker():
2990 try:
2991 lock.acquire()
2992 for d in diff_iter:
2993 lock.release()
2994 start = time.time()
2995 d.ComputePatch()
2996 dur = time.time() - start
2997 lock.acquire()
2998
2999 tf, sf, patch = d.GetPatch()
3000 if sf.name == tf.name:
3001 name = tf.name
3002 else:
3003 name = "%s (%s)" % (tf.name, sf.name)
3004 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003005 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003006 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003007 logger.info(
3008 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3009 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003010 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003011 except Exception:
3012 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003013 raise
3014
3015 # start worker threads; wait for them all to finish.
3016 threads = [threading.Thread(target=worker)
3017 for i in range(OPTIONS.worker_threads)]
3018 for th in threads:
3019 th.start()
3020 while threads:
3021 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003022
3023
Dan Albert8b72aef2015-03-23 19:13:21 -07003024class BlockDifference(object):
3025 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003026 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003027 self.tgt = tgt
3028 self.src = src
3029 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003030 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003031 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003032
Tao Baodd2a5892015-03-12 12:32:37 -07003033 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003034 version = max(
3035 int(i) for i in
3036 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003037 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003038 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003039
Tianjie Xu41976c72019-07-03 13:57:01 -07003040 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3041 version=self.version,
3042 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003043 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003044 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003045 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003046 self.touched_src_ranges = b.touched_src_ranges
3047 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003048
Yifan Hong10c530d2018-12-27 17:34:18 -08003049 # On devices with dynamic partitions, for new partitions,
3050 # src is None but OPTIONS.source_info_dict is not.
3051 if OPTIONS.source_info_dict is None:
3052 is_dynamic_build = OPTIONS.info_dict.get(
3053 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003054 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003055 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003056 is_dynamic_build = OPTIONS.source_info_dict.get(
3057 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003058 is_dynamic_source = partition in shlex.split(
3059 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003060
Yifan Hongbb2658d2019-01-25 12:30:58 -08003061 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003062 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3063
Yifan Hongbb2658d2019-01-25 12:30:58 -08003064 # For dynamic partitions builds, check partition list in both source
3065 # and target build because new partitions may be added, and existing
3066 # partitions may be removed.
3067 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3068
Yifan Hong10c530d2018-12-27 17:34:18 -08003069 if is_dynamic:
3070 self.device = 'map_partition("%s")' % partition
3071 else:
3072 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003073 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3074 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003075 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003076 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3077 OPTIONS.source_info_dict)
3078 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003079
Tao Baod8d14be2016-02-04 14:26:02 -08003080 @property
3081 def required_cache(self):
3082 return self._required_cache
3083
Tao Bao76def242017-11-21 09:25:31 -08003084 def WriteScript(self, script, output_zip, progress=None,
3085 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003086 if not self.src:
3087 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003088 script.Print("Patching %s image unconditionally..." % (self.partition,))
3089 else:
3090 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003091
Dan Albert8b72aef2015-03-23 19:13:21 -07003092 if progress:
3093 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003094 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003095
3096 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003097 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003098
Tao Bao9bc6bb22015-11-09 16:58:28 -08003099 def WriteStrictVerifyScript(self, script):
3100 """Verify all the blocks in the care_map, including clobbered blocks.
3101
3102 This differs from the WriteVerifyScript() function: a) it prints different
3103 error messages; b) it doesn't allow half-way updated images to pass the
3104 verification."""
3105
3106 partition = self.partition
3107 script.Print("Verifying %s..." % (partition,))
3108 ranges = self.tgt.care_map
3109 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003110 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003111 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3112 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003113 self.device, ranges_str,
3114 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003115 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003116 script.AppendExtra("")
3117
Tao Baod522bdc2016-04-12 15:53:16 -07003118 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003119 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003120
3121 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003122 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003123 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003124
3125 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003126 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003127 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003128 ranges = self.touched_src_ranges
3129 expected_sha1 = self.touched_src_sha1
3130 else:
3131 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3132 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003133
3134 # No blocks to be checked, skipping.
3135 if not ranges:
3136 return
3137
Tao Bao5ece99d2015-05-12 11:42:31 -07003138 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003139 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003140 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003141 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3142 '"%s.patch.dat")) then' % (
3143 self.device, ranges_str, expected_sha1,
3144 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003145 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003146 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003147
Tianjie Xufc3422a2015-12-15 11:53:59 -08003148 if self.version >= 4:
3149
3150 # Bug: 21124327
3151 # When generating incrementals for the system and vendor partitions in
3152 # version 4 or newer, explicitly check the first block (which contains
3153 # the superblock) of the partition to see if it's what we expect. If
3154 # this check fails, give an explicit log message about the partition
3155 # having been remounted R/W (the most likely explanation).
3156 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003157 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003158
3159 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003160 if partition == "system":
3161 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3162 else:
3163 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003164 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003165 'ifelse (block_image_recover({device}, "{ranges}") && '
3166 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003167 'package_extract_file("{partition}.transfer.list"), '
3168 '"{partition}.new.dat", "{partition}.patch.dat"), '
3169 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003170 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003171 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003172 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003173
Tao Baodd2a5892015-03-12 12:32:37 -07003174 # Abort the OTA update. Note that the incremental OTA cannot be applied
3175 # even if it may match the checksum of the target partition.
3176 # a) If version < 3, operations like move and erase will make changes
3177 # unconditionally and damage the partition.
3178 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003179 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003180 if partition == "system":
3181 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3182 else:
3183 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3184 script.AppendExtra((
3185 'abort("E%d: %s partition has unexpected contents");\n'
3186 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003187
Yifan Hong10c530d2018-12-27 17:34:18 -08003188 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003189 partition = self.partition
3190 script.Print('Verifying the updated %s image...' % (partition,))
3191 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3192 ranges = self.tgt.care_map
3193 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003194 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003195 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003196 self.device, ranges_str,
3197 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003198
3199 # Bug: 20881595
3200 # Verify that extended blocks are really zeroed out.
3201 if self.tgt.extended:
3202 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003203 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003204 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003205 self.device, ranges_str,
3206 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003207 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003208 if partition == "system":
3209 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3210 else:
3211 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003212 script.AppendExtra(
3213 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003214 ' abort("E%d: %s partition has unexpected non-zero contents after '
3215 'OTA update");\n'
3216 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003217 else:
3218 script.Print('Verified the updated %s image.' % (partition,))
3219
Tianjie Xu209db462016-05-24 17:34:52 -07003220 if partition == "system":
3221 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3222 else:
3223 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3224
Tao Bao5fcaaef2015-06-01 13:40:49 -07003225 script.AppendExtra(
3226 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003227 ' abort("E%d: %s partition has unexpected contents after OTA '
3228 'update");\n'
3229 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003230
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003231 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003232 ZipWrite(output_zip,
3233 '{}.transfer.list'.format(self.path),
3234 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003235
Tao Bao76def242017-11-21 09:25:31 -08003236 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3237 # its size. Quailty 9 almost triples the compression time but doesn't
3238 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003239 # zip | brotli(quality 6) | brotli(quality 9)
3240 # compressed_size: 942M | 869M (~8% reduced) | 854M
3241 # compression_time: 75s | 265s | 719s
3242 # decompression_time: 15s | 25s | 25s
3243
3244 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003245 brotli_cmd = ['brotli', '--quality=6',
3246 '--output={}.new.dat.br'.format(self.path),
3247 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003248 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003249 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003250
3251 new_data_name = '{}.new.dat.br'.format(self.partition)
3252 ZipWrite(output_zip,
3253 '{}.new.dat.br'.format(self.path),
3254 new_data_name,
3255 compress_type=zipfile.ZIP_STORED)
3256 else:
3257 new_data_name = '{}.new.dat'.format(self.partition)
3258 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3259
Dan Albert8e0178d2015-01-27 15:53:15 -08003260 ZipWrite(output_zip,
3261 '{}.patch.dat'.format(self.path),
3262 '{}.patch.dat'.format(self.partition),
3263 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003264
Tianjie Xu209db462016-05-24 17:34:52 -07003265 if self.partition == "system":
3266 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3267 else:
3268 code = ErrorCode.VENDOR_UPDATE_FAILURE
3269
Yifan Hong10c530d2018-12-27 17:34:18 -08003270 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003271 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003272 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003273 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003274 device=self.device, partition=self.partition,
3275 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003276 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003277
Kelvin Zhang0876c412020-06-23 15:06:58 -04003278 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003279 data = source.ReadRangeSet(ranges)
3280 ctx = sha1()
3281
3282 for p in data:
3283 ctx.update(p)
3284
3285 return ctx.hexdigest()
3286
Kelvin Zhang0876c412020-06-23 15:06:58 -04003287 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003288 """Return the hash value for all zero blocks."""
3289 zero_block = '\x00' * 4096
3290 ctx = sha1()
3291 for _ in range(num_blocks):
3292 ctx.update(zero_block)
3293
3294 return ctx.hexdigest()
3295
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003296
Tianjie Xu41976c72019-07-03 13:57:01 -07003297# Expose these two classes to support vendor-specific scripts
3298DataImage = images.DataImage
3299EmptyImage = images.EmptyImage
3300
Tao Bao76def242017-11-21 09:25:31 -08003301
Doug Zongker96a57e72010-09-26 14:57:41 -07003302# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003303PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003304 "ext4": "EMMC",
3305 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003306 "f2fs": "EMMC",
3307 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003308}
Doug Zongker96a57e72010-09-26 14:57:41 -07003309
Kelvin Zhang0876c412020-06-23 15:06:58 -04003310
Yifan Hongbdb32012020-05-07 12:38:53 -07003311def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3312 """
3313 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3314 backwards compatibility. It aborts if the fstab entry has slotselect option
3315 (unless check_no_slot is explicitly set to False).
3316 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003317 fstab = info["fstab"]
3318 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003319 if check_no_slot:
3320 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003321 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003322 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3323 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003324 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003325
3326
Yifan Hongbdb32012020-05-07 12:38:53 -07003327def GetTypeAndDeviceExpr(mount_point, info):
3328 """
3329 Return the filesystem of the partition, and an edify expression that evaluates
3330 to the device at runtime.
3331 """
3332 fstab = info["fstab"]
3333 if fstab:
3334 p = fstab[mount_point]
3335 device_expr = '"%s"' % fstab[mount_point].device
3336 if p.slotselect:
3337 device_expr = 'add_slot_suffix(%s)' % device_expr
3338 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003339 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003340
3341
3342def GetEntryForDevice(fstab, device):
3343 """
3344 Returns:
3345 The first entry in fstab whose device is the given value.
3346 """
3347 if not fstab:
3348 return None
3349 for mount_point in fstab:
3350 if fstab[mount_point].device == device:
3351 return fstab[mount_point]
3352 return None
3353
Kelvin Zhang0876c412020-06-23 15:06:58 -04003354
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003355def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003356 """Parses and converts a PEM-encoded certificate into DER-encoded.
3357
3358 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3359
3360 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003361 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003362 """
3363 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003364 save = False
3365 for line in data.split("\n"):
3366 if "--END CERTIFICATE--" in line:
3367 break
3368 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003369 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003370 if "--BEGIN CERTIFICATE--" in line:
3371 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003372 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003373 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003374
Tao Bao04e1f012018-02-04 12:13:35 -08003375
3376def ExtractPublicKey(cert):
3377 """Extracts the public key (PEM-encoded) from the given certificate file.
3378
3379 Args:
3380 cert: The certificate filename.
3381
3382 Returns:
3383 The public key string.
3384
3385 Raises:
3386 AssertionError: On non-zero return from 'openssl'.
3387 """
3388 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3389 # While openssl 1.1 writes the key into the given filename followed by '-out',
3390 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3391 # stdout instead.
3392 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3393 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3394 pubkey, stderrdata = proc.communicate()
3395 assert proc.returncode == 0, \
3396 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3397 return pubkey
3398
3399
Tao Bao1ac886e2019-06-26 11:58:22 -07003400def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003401 """Extracts the AVB public key from the given public or private key.
3402
3403 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003404 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003405 key: The input key file, which should be PEM-encoded public or private key.
3406
3407 Returns:
3408 The path to the extracted AVB public key file.
3409 """
3410 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3411 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003412 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003413 return output
3414
3415
Doug Zongker412c02f2014-02-13 10:58:24 -08003416def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3417 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003418 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003419
Tao Bao6d5d6232018-03-09 17:04:42 -08003420 Most of the space in the boot and recovery images is just the kernel, which is
3421 identical for the two, so the resulting patch should be efficient. Add it to
3422 the output zip, along with a shell script that is run from init.rc on first
3423 boot to actually do the patching and install the new recovery image.
3424
3425 Args:
3426 input_dir: The top-level input directory of the target-files.zip.
3427 output_sink: The callback function that writes the result.
3428 recovery_img: File object for the recovery image.
3429 boot_img: File objects for the boot image.
3430 info_dict: A dict returned by common.LoadInfoDict() on the input
3431 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003432 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003433 if info_dict is None:
3434 info_dict = OPTIONS.info_dict
3435
Tao Bao6d5d6232018-03-09 17:04:42 -08003436 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003437 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3438
3439 if board_uses_vendorimage:
3440 # In this case, the output sink is rooted at VENDOR
3441 recovery_img_path = "etc/recovery.img"
3442 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3443 sh_dir = "bin"
3444 else:
3445 # In this case the output sink is rooted at SYSTEM
3446 recovery_img_path = "vendor/etc/recovery.img"
3447 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3448 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003449
Tao Baof2cffbd2015-07-22 12:33:18 -07003450 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003451 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003452
3453 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003454 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003455 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003456 # With system-root-image, boot and recovery images will have mismatching
3457 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3458 # to handle such a case.
3459 if system_root_image:
3460 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003461 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003462 assert not os.path.exists(path)
3463 else:
3464 diff_program = ["imgdiff"]
3465 if os.path.exists(path):
3466 diff_program.append("-b")
3467 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003468 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003469 else:
3470 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003471
3472 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3473 _, _, patch = d.ComputePatch()
3474 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003475
Dan Albertebb19aa2015-03-27 19:11:53 -07003476 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003477 # The following GetTypeAndDevice()s need to use the path in the target
3478 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003479 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3480 check_no_slot=False)
3481 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3482 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003483 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003484 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003485
Tao Baof2cffbd2015-07-22 12:33:18 -07003486 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003487
3488 # Note that we use /vendor to refer to the recovery resources. This will
3489 # work for a separate vendor partition mounted at /vendor or a
3490 # /system/vendor subdirectory on the system partition, for which init will
3491 # create a symlink from /vendor to /system/vendor.
3492
3493 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003494if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3495 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003496 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003497 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3498 log -t recovery "Installing new recovery image: succeeded" || \\
3499 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003500else
3501 log -t recovery "Recovery image already installed"
3502fi
3503""" % {'type': recovery_type,
3504 'device': recovery_device,
3505 'sha1': recovery_img.sha1,
3506 'size': recovery_img.size}
3507 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003508 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003509if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3510 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003511 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003512 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3513 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3514 log -t recovery "Installing new recovery image: succeeded" || \\
3515 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003516else
3517 log -t recovery "Recovery image already installed"
3518fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003519""" % {'boot_size': boot_img.size,
3520 'boot_sha1': boot_img.sha1,
3521 'recovery_size': recovery_img.size,
3522 'recovery_sha1': recovery_img.sha1,
3523 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003524 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003525 'recovery_type': recovery_type,
3526 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003527 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003528
Bill Peckhame868aec2019-09-17 17:06:47 -07003529 # The install script location moved from /system/etc to /system/bin in the L
3530 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3531 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003532
Tao Bao32fcdab2018-10-12 10:30:39 -07003533 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003534
Tao Baoda30cfa2017-12-01 16:19:46 -08003535 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003536
3537
3538class DynamicPartitionUpdate(object):
3539 def __init__(self, src_group=None, tgt_group=None, progress=None,
3540 block_difference=None):
3541 self.src_group = src_group
3542 self.tgt_group = tgt_group
3543 self.progress = progress
3544 self.block_difference = block_difference
3545
3546 @property
3547 def src_size(self):
3548 if not self.block_difference:
3549 return 0
3550 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3551
3552 @property
3553 def tgt_size(self):
3554 if not self.block_difference:
3555 return 0
3556 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3557
3558 @staticmethod
3559 def _GetSparseImageSize(img):
3560 if not img:
3561 return 0
3562 return img.blocksize * img.total_blocks
3563
3564
3565class DynamicGroupUpdate(object):
3566 def __init__(self, src_size=None, tgt_size=None):
3567 # None: group does not exist. 0: no size limits.
3568 self.src_size = src_size
3569 self.tgt_size = tgt_size
3570
3571
3572class DynamicPartitionsDifference(object):
3573 def __init__(self, info_dict, block_diffs, progress_dict=None,
3574 source_info_dict=None):
3575 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003576 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003577
3578 self._remove_all_before_apply = False
3579 if source_info_dict is None:
3580 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003581 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003582
Tao Baof1113e92019-06-18 12:10:14 -07003583 block_diff_dict = collections.OrderedDict(
3584 [(e.partition, e) for e in block_diffs])
3585
Yifan Hong10c530d2018-12-27 17:34:18 -08003586 assert len(block_diff_dict) == len(block_diffs), \
3587 "Duplicated BlockDifference object for {}".format(
3588 [partition for partition, count in
3589 collections.Counter(e.partition for e in block_diffs).items()
3590 if count > 1])
3591
Yifan Hong79997e52019-01-23 16:56:19 -08003592 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003593
3594 for p, block_diff in block_diff_dict.items():
3595 self._partition_updates[p] = DynamicPartitionUpdate()
3596 self._partition_updates[p].block_difference = block_diff
3597
3598 for p, progress in progress_dict.items():
3599 if p in self._partition_updates:
3600 self._partition_updates[p].progress = progress
3601
3602 tgt_groups = shlex.split(info_dict.get(
3603 "super_partition_groups", "").strip())
3604 src_groups = shlex.split(source_info_dict.get(
3605 "super_partition_groups", "").strip())
3606
3607 for g in tgt_groups:
3608 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003609 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003610 assert p in self._partition_updates, \
3611 "{} is in target super_{}_partition_list but no BlockDifference " \
3612 "object is provided.".format(p, g)
3613 self._partition_updates[p].tgt_group = g
3614
3615 for g in src_groups:
3616 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003617 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003618 assert p in self._partition_updates, \
3619 "{} is in source super_{}_partition_list but no BlockDifference " \
3620 "object is provided.".format(p, g)
3621 self._partition_updates[p].src_group = g
3622
Yifan Hong45433e42019-01-18 13:55:25 -08003623 target_dynamic_partitions = set(shlex.split(info_dict.get(
3624 "dynamic_partition_list", "").strip()))
3625 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3626 if u.tgt_size)
3627 assert block_diffs_with_target == target_dynamic_partitions, \
3628 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3629 list(target_dynamic_partitions), list(block_diffs_with_target))
3630
3631 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3632 "dynamic_partition_list", "").strip()))
3633 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3634 if u.src_size)
3635 assert block_diffs_with_source == source_dynamic_partitions, \
3636 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3637 list(source_dynamic_partitions), list(block_diffs_with_source))
3638
Yifan Hong10c530d2018-12-27 17:34:18 -08003639 if self._partition_updates:
3640 logger.info("Updating dynamic partitions %s",
3641 self._partition_updates.keys())
3642
Yifan Hong79997e52019-01-23 16:56:19 -08003643 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003644
3645 for g in tgt_groups:
3646 self._group_updates[g] = DynamicGroupUpdate()
3647 self._group_updates[g].tgt_size = int(info_dict.get(
3648 "super_%s_group_size" % g, "0").strip())
3649
3650 for g in src_groups:
3651 if g not in self._group_updates:
3652 self._group_updates[g] = DynamicGroupUpdate()
3653 self._group_updates[g].src_size = int(source_info_dict.get(
3654 "super_%s_group_size" % g, "0").strip())
3655
3656 self._Compute()
3657
3658 def WriteScript(self, script, output_zip, write_verify_script=False):
3659 script.Comment('--- Start patching dynamic partitions ---')
3660 for p, u in self._partition_updates.items():
3661 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3662 script.Comment('Patch partition %s' % p)
3663 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3664 write_verify_script=False)
3665
3666 op_list_path = MakeTempFile()
3667 with open(op_list_path, 'w') as f:
3668 for line in self._op_list:
3669 f.write('{}\n'.format(line))
3670
3671 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3672
3673 script.Comment('Update dynamic partition metadata')
3674 script.AppendExtra('assert(update_dynamic_partitions('
3675 'package_extract_file("dynamic_partitions_op_list")));')
3676
3677 if write_verify_script:
3678 for p, u in self._partition_updates.items():
3679 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3680 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003681 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003682
3683 for p, u in self._partition_updates.items():
3684 if u.tgt_size and u.src_size <= u.tgt_size:
3685 script.Comment('Patch partition %s' % p)
3686 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3687 write_verify_script=write_verify_script)
3688 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003689 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003690
3691 script.Comment('--- End patching dynamic partitions ---')
3692
3693 def _Compute(self):
3694 self._op_list = list()
3695
3696 def append(line):
3697 self._op_list.append(line)
3698
3699 def comment(line):
3700 self._op_list.append("# %s" % line)
3701
3702 if self._remove_all_before_apply:
3703 comment('Remove all existing dynamic partitions and groups before '
3704 'applying full OTA')
3705 append('remove_all_groups')
3706
3707 for p, u in self._partition_updates.items():
3708 if u.src_group and not u.tgt_group:
3709 append('remove %s' % p)
3710
3711 for p, u in self._partition_updates.items():
3712 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3713 comment('Move partition %s from %s to default' % (p, u.src_group))
3714 append('move %s default' % p)
3715
3716 for p, u in self._partition_updates.items():
3717 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3718 comment('Shrink partition %s from %d to %d' %
3719 (p, u.src_size, u.tgt_size))
3720 append('resize %s %s' % (p, u.tgt_size))
3721
3722 for g, u in self._group_updates.items():
3723 if u.src_size is not None and u.tgt_size is None:
3724 append('remove_group %s' % g)
3725 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003726 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003727 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3728 append('resize_group %s %d' % (g, u.tgt_size))
3729
3730 for g, u in self._group_updates.items():
3731 if u.src_size is None and u.tgt_size is not None:
3732 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3733 append('add_group %s %d' % (g, u.tgt_size))
3734 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003735 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003736 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3737 append('resize_group %s %d' % (g, u.tgt_size))
3738
3739 for p, u in self._partition_updates.items():
3740 if u.tgt_group and not u.src_group:
3741 comment('Add partition %s to group %s' % (p, u.tgt_group))
3742 append('add %s %s' % (p, u.tgt_group))
3743
3744 for p, u in self._partition_updates.items():
3745 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003746 comment('Grow partition %s from %d to %d' %
3747 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003748 append('resize %s %d' % (p, u.tgt_size))
3749
3750 for p, u in self._partition_updates.items():
3751 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3752 comment('Move partition %s from default to %s' %
3753 (p, u.tgt_group))
3754 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003755
3756
jiajia tangf3f842b2021-03-17 21:49:44 +08003757def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003758 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003759 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003760
3761 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003762 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003763
3764 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003765 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003766 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003767 tmp_dir = MakeTempDir('boot_', suffix='.img')
3768 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003769 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3770 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003771 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3772 if not os.path.isfile(ramdisk):
3773 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3774 return None
3775 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003776 if ramdisk_format == RamdiskFormat.LZ4:
3777 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3778 elif ramdisk_format == RamdiskFormat.GZ:
3779 with open(ramdisk, 'rb') as input_stream:
3780 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003781 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3782 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003783 p2.wait()
3784 else:
3785 logger.error('Only support lz4 or minigzip ramdisk format.')
3786 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003787
3788 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3789 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3790 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3791 # the host environment.
3792 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003793 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003794
Yifan Hongc65a0542021-01-07 14:21:01 -08003795 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3796 prop_file = os.path.join(extracted_ramdisk, search_path)
3797 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003798 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003799 logger.warning(
3800 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003801
Yifan Hong7dc51172021-01-12 11:27:39 -08003802 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003803
Yifan Hong85ac5012021-01-07 14:43:46 -08003804 except ExternalError as e:
3805 logger.warning('Unable to get boot image build props: %s', e)
3806 return None
3807
3808
3809def GetBootImageTimestamp(boot_img):
3810 """
3811 Get timestamp from ramdisk within the boot image
3812
3813 Args:
3814 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3815
3816 Return:
3817 An integer that corresponds to the timestamp of the boot image, or None
3818 if file has unknown format. Raise exception if an unexpected error has
3819 occurred.
3820 """
3821 prop_file = GetBootImageBuildProp(boot_img)
3822 if not prop_file:
3823 return None
3824
3825 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3826 if props is None:
3827 return None
3828
3829 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003830 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3831 if timestamp:
3832 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003833 logger.warning(
3834 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003835 return None
3836
3837 except ExternalError as e:
3838 logger.warning('Unable to get boot image timestamp: %s', e)
3839 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003840
3841
3842def GetCareMap(which, imgname):
3843 """Returns the care_map string for the given partition.
3844
3845 Args:
3846 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3847 imgname: The filename of the image.
3848
3849 Returns:
3850 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3851 RangeSet; or None.
3852 """
3853 assert which in PARTITIONS_WITH_CARE_MAP
3854
3855 # which + "_image_size" contains the size that the actual filesystem image
3856 # resides in, which is all that needs to be verified. The additional blocks in
3857 # the image file contain verity metadata, by reading which would trigger
3858 # invalid reads.
3859 image_size = OPTIONS.info_dict.get(which + "_image_size")
3860 if not image_size:
3861 return None
3862
David Anderson9e95a022021-08-31 21:32:45 -07003863 disable_sparse = OPTIONS.info_dict.get(which + "_disable_sparse")
3864
Kelvin Zhang27324132021-03-22 15:38:38 -04003865 image_blocks = int(image_size) // 4096 - 1
3866 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3867
3868 # For sparse images, we will only check the blocks that are listed in the care
3869 # map, i.e. the ones with meaningful data.
David Anderson9e95a022021-08-31 21:32:45 -07003870 if "extfs_sparse_flag" in OPTIONS.info_dict and not disable_sparse:
Kelvin Zhang27324132021-03-22 15:38:38 -04003871 simg = sparse_img.SparseImage(imgname)
3872 care_map_ranges = simg.care_map.intersect(
3873 rangelib.RangeSet("0-{}".format(image_blocks)))
3874
3875 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3876 # image.
3877 else:
3878 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3879
3880 return [which, care_map_ranges.to_string_raw()]
3881
3882
3883def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3884 """Generates and adds care_map.pb for a/b partition that has care_map.
3885
3886 Args:
3887 output_file: The output zip file (needs to be already open),
3888 or file path to write care_map.pb.
3889 ab_partitions: The list of A/B partitions.
3890 image_paths: A map from the partition name to the image path.
3891 """
3892 if not output_file:
3893 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3894
3895 care_map_list = []
3896 for partition in ab_partitions:
3897 partition = partition.strip()
3898 if partition not in PARTITIONS_WITH_CARE_MAP:
3899 continue
3900
3901 verity_block_device = "{}_verity_block_device".format(partition)
3902 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3903 if (verity_block_device in OPTIONS.info_dict or
3904 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3905 if partition not in image_paths:
3906 logger.warning('Potential partition with care_map missing from images: %s',
3907 partition)
3908 continue
3909 image_path = image_paths[partition]
3910 if not os.path.exists(image_path):
3911 raise ExternalError('Expected image at path {}'.format(image_path))
3912
3913 care_map = GetCareMap(partition, image_path)
3914 if not care_map:
3915 continue
3916 care_map_list += care_map
3917
3918 # adds fingerprint field to the care_map
3919 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3920 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3921 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3922 "ro.{}.build.thumbprint".format(partition)]
3923
3924 present_props = [x for x in prop_name_list if
3925 partition_props and partition_props.GetProp(x)]
3926 if not present_props:
3927 logger.warning(
3928 "fingerprint is not present for partition %s", partition)
3929 property_id, fingerprint = "unknown", "unknown"
3930 else:
3931 property_id = present_props[0]
3932 fingerprint = partition_props.GetProp(property_id)
3933 care_map_list += [property_id, fingerprint]
3934
3935 if not care_map_list:
3936 return
3937
3938 # Converts the list into proto buf message by calling care_map_generator; and
3939 # writes the result to a temp file.
3940 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3941 suffix=".txt")
3942 with open(temp_care_map_text, 'w') as text_file:
3943 text_file.write('\n'.join(care_map_list))
3944
3945 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3946 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3947 RunAndCheckOutput(care_map_gen_cmd)
3948
3949 if not isinstance(output_file, zipfile.ZipFile):
3950 shutil.copy(temp_care_map, output_file)
3951 return
3952 # output_file is a zip file
3953 care_map_path = "META/care_map.pb"
3954 if care_map_path in output_file.namelist():
3955 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3956 # replace_updated_files_list used by add_img_to_target_files
3957 if not OPTIONS.replace_updated_files_list:
3958 OPTIONS.replace_updated_files_list = []
3959 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3960 OPTIONS.replace_updated_files_list.append(care_map_path)
3961 else:
3962 ZipWrite(output_file, temp_care_map, arcname=care_map_path)
Kelvin Zhang26390482021-11-02 14:31:10 -07003963
3964
3965def IsSparseImage(filepath):
3966 with open(filepath, 'rb') as fp:
3967 # Magic for android sparse image format
3968 # https://source.android.com/devices/bootloader/images
3969 return fp.read(4) == b'\x3A\xFF\x26\xED'