blob: 0711af52701e7a353cf64e2b53ae059431d9149c [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
Alex Klyubin9667b182015-12-10 13:38:50 -080071 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.extra_signapk_args = []
73 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080074 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080075 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070076 self.public_key_suffix = ".x509.pem"
77 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070078 # use otatools built boot_signer by default
79 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070080 self.boot_signer_args = []
81 self.verity_signer_path = None
82 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070083 self.verbose = False
84 self.tempfiles = []
85 self.device_specific = None
86 self.extras = {}
87 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070088 self.source_info_dict = None
89 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070090 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070091 # Stash size cannot exceed cache_size * threshold.
92 self.cache_size = None
93 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070094 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070095 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -070096
97
98OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070099
Tao Bao71197512018-10-11 14:08:45 -0700100# The block size that's used across the releasetools scripts.
101BLOCK_SIZE = 4096
102
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800103# Values for "certificate" in apkcerts that mean special things.
104SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
105
Tao Bao5cc0abb2019-03-21 10:18:05 -0700106# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
107# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800108# descriptor into vbmeta.img. When adding a new entry here, the
109# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
110# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000111AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
112 'system', 'system_ext', 'vendor', 'vendor_boot',
113 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800114
Tao Bao08c190f2019-06-03 23:07:58 -0700115# Chained VBMeta partitions.
116AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
117
Tianjie Xu861f4132018-09-12 11:49:33 -0700118# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400119PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700120 'system',
121 'vendor',
122 'product',
123 'system_ext',
124 'odm',
125 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700126 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400127]
Tianjie Xu861f4132018-09-12 11:49:33 -0700128
Yifan Hong5057b952021-01-07 14:09:57 -0800129# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800130PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800131
Yifan Hongc65a0542021-01-07 14:21:01 -0800132# See sysprop.mk. If file is moved, add new search paths here; don't remove
133# existing search paths.
134RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700135
Kelvin Zhang563750f2021-04-28 12:46:17 -0400136
Tianjie Xu209db462016-05-24 17:34:52 -0700137class ErrorCode(object):
138 """Define error_codes for failures that happen during the actual
139 update package installation.
140
141 Error codes 0-999 are reserved for failures before the package
142 installation (i.e. low battery, package verification failure).
143 Detailed code in 'bootable/recovery/error_code.h' """
144
145 SYSTEM_VERIFICATION_FAILURE = 1000
146 SYSTEM_UPDATE_FAILURE = 1001
147 SYSTEM_UNEXPECTED_CONTENTS = 1002
148 SYSTEM_NONZERO_CONTENTS = 1003
149 SYSTEM_RECOVER_FAILURE = 1004
150 VENDOR_VERIFICATION_FAILURE = 2000
151 VENDOR_UPDATE_FAILURE = 2001
152 VENDOR_UNEXPECTED_CONTENTS = 2002
153 VENDOR_NONZERO_CONTENTS = 2003
154 VENDOR_RECOVER_FAILURE = 2004
155 OEM_PROP_MISMATCH = 3000
156 FINGERPRINT_MISMATCH = 3001
157 THUMBPRINT_MISMATCH = 3002
158 OLDER_BUILD = 3003
159 DEVICE_MISMATCH = 3004
160 BAD_PATCH_FILE = 3005
161 INSUFFICIENT_CACHE_SPACE = 3006
162 TUNE_PARTITION_FAILURE = 3007
163 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800164
Tao Bao80921982018-03-21 21:02:19 -0700165
Dan Albert8b72aef2015-03-23 19:13:21 -0700166class ExternalError(RuntimeError):
167 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700168
169
Tao Bao32fcdab2018-10-12 10:30:39 -0700170def InitLogging():
171 DEFAULT_LOGGING_CONFIG = {
172 'version': 1,
173 'disable_existing_loggers': False,
174 'formatters': {
175 'standard': {
176 'format':
177 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
178 'datefmt': '%Y-%m-%d %H:%M:%S',
179 },
180 },
181 'handlers': {
182 'default': {
183 'class': 'logging.StreamHandler',
184 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700185 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 },
187 },
188 'loggers': {
189 '': {
190 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700192 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700193 }
194 }
195 }
196 env_config = os.getenv('LOGGING_CONFIG')
197 if env_config:
198 with open(env_config) as f:
199 config = json.load(f)
200 else:
201 config = DEFAULT_LOGGING_CONFIG
202
203 # Increase the logging level for verbose mode.
204 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700205 config = copy.deepcopy(config)
206 config['handlers']['default']['level'] = 'INFO'
207
208 if OPTIONS.logfile:
209 config = copy.deepcopy(config)
210 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400211 'class': 'logging.FileHandler',
212 'formatter': 'standard',
213 'level': 'INFO',
214 'mode': 'w',
215 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700216 }
217 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700218
219 logging.config.dictConfig(config)
220
221
Yifan Hong8e332ff2020-07-29 17:51:55 -0700222def SetHostToolLocation(tool_name, location):
223 OPTIONS.host_tools[tool_name] = location
224
Kelvin Zhang563750f2021-04-28 12:46:17 -0400225
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900226def FindHostToolPath(tool_name):
227 """Finds the path to the host tool.
228
229 Args:
230 tool_name: name of the tool to find
231 Returns:
232 path to the tool if found under either one of the host_tools map or under
233 the same directory as this binary is located at. If not found, tool_name
234 is returned.
235 """
236 if tool_name in OPTIONS.host_tools:
237 return OPTIONS.host_tools[tool_name]
238
239 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
240 tool_path = os.path.join(my_dir, tool_name)
241 if os.path.exists(tool_path):
242 return tool_path
243
244 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700245
Kelvin Zhang563750f2021-04-28 12:46:17 -0400246
Tao Bao39451582017-05-04 11:10:47 -0700247def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700248 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700249
Tao Bao73dd4f42018-10-04 16:25:33 -0700250 Args:
251 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700252 verbose: Whether the commands should be shown. Default to the global
253 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700254 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
255 stdin, etc. stdout and stderr will default to subprocess.PIPE and
256 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800257 universal_newlines will default to True, as most of the users in
258 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259
260 Returns:
261 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700262 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700263 if 'stdout' not in kwargs and 'stderr' not in kwargs:
264 kwargs['stdout'] = subprocess.PIPE
265 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800266 if 'universal_newlines' not in kwargs:
267 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700268
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900269 if args:
270 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700271 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900272 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Kelvin Zhang766eea72021-06-03 09:36:08 -0400274 if verbose is None:
275 verbose = OPTIONS.verbose
276
Tao Bao32fcdab2018-10-12 10:30:39 -0700277 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400278 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700279 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700280 return subprocess.Popen(args, **kwargs)
281
282
Tao Bao986ee862018-10-04 15:46:16 -0700283def RunAndCheckOutput(args, verbose=None, **kwargs):
284 """Runs the given command and returns the output.
285
286 Args:
287 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700288 verbose: Whether the commands should be shown. Default to the global
289 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700290 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
291 stdin, etc. stdout and stderr will default to subprocess.PIPE and
292 subprocess.STDOUT respectively unless caller specifies any of them.
293
294 Returns:
295 The output string.
296
297 Raises:
298 ExternalError: On non-zero exit from the command.
299 """
Tao Bao986ee862018-10-04 15:46:16 -0700300 proc = Run(args, verbose=verbose, **kwargs)
301 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800302 if output is None:
303 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700304 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400305 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700306 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700307 if proc.returncode != 0:
308 raise ExternalError(
309 "Failed to run command '{}' (exit code {}):\n{}".format(
310 args, proc.returncode, output))
311 return output
312
313
Tao Baoc765cca2018-01-31 17:32:40 -0800314def RoundUpTo4K(value):
315 rounded_up = value + 4095
316 return rounded_up - (rounded_up % 4096)
317
318
Ying Wang7e6d4e42010-12-13 16:25:36 -0800319def CloseInheritedPipes():
320 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
321 before doing other work."""
322 if platform.system() != "Darwin":
323 return
324 for d in range(3, 1025):
325 try:
326 stat = os.fstat(d)
327 if stat is not None:
328 pipebit = stat[0] & 0x1000
329 if pipebit != 0:
330 os.close(d)
331 except OSError:
332 pass
333
334
Tao Bao1c320f82019-10-04 23:25:12 -0700335class BuildInfo(object):
336 """A class that holds the information for a given build.
337
338 This class wraps up the property querying for a given source or target build.
339 It abstracts away the logic of handling OEM-specific properties, and caches
340 the commonly used properties such as fingerprint.
341
342 There are two types of info dicts: a) build-time info dict, which is generated
343 at build time (i.e. included in a target_files zip); b) OEM info dict that is
344 specified at package generation time (via command line argument
345 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
346 having "oem_fingerprint_properties" in build-time info dict), all the queries
347 would be answered based on build-time info dict only. Otherwise if using
348 OEM-specific properties, some of them will be calculated from two info dicts.
349
350 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800351 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700352
353 Attributes:
354 info_dict: The build-time info dict.
355 is_ab: Whether it's a build that uses A/B OTA.
356 oem_dicts: A list of OEM dicts.
357 oem_props: A list of OEM properties that should be read from OEM dicts; None
358 if the build doesn't use any OEM-specific property.
359 fingerprint: The fingerprint of the build, which would be calculated based
360 on OEM properties if applicable.
361 device: The device name, which could come from OEM dicts if applicable.
362 """
363
364 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
365 "ro.product.manufacturer", "ro.product.model",
366 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
368 "product", "odm", "vendor", "system_ext", "system"]
369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
370 "product", "product_services", "odm", "vendor", "system"]
371 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700372
Tianjiefdda51d2021-05-05 14:46:35 -0700373 # The length of vbmeta digest to append to the fingerprint
374 _VBMETA_DIGEST_SIZE_USED = 8
375
376 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700377 """Initializes a BuildInfo instance with the given dicts.
378
379 Note that it only wraps up the given dicts, without making copies.
380
381 Arguments:
382 info_dict: The build-time info dict.
383 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
384 that it always uses the first dict to calculate the fingerprint or the
385 device name. The rest would be used for asserting OEM properties only
386 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700387 use_legacy_id: Use the legacy build id to construct the fingerprint. This
388 is used when we need a BuildInfo class, while the vbmeta digest is
389 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700390
391 Raises:
392 ValueError: On invalid inputs.
393 """
394 self.info_dict = info_dict
395 self.oem_dicts = oem_dicts
396
397 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700398 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700399
Hongguang Chend7c160f2020-05-03 21:24:26 -0700400 # Skip _oem_props if oem_dicts is None to use BuildInfo in
401 # sign_target_files_apks
402 if self.oem_dicts:
403 self._oem_props = info_dict.get("oem_fingerprint_properties")
404 else:
405 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700406
Daniel Normand5fe8622020-01-08 17:01:11 -0800407 def check_fingerprint(fingerprint):
408 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
409 raise ValueError(
410 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
411 "3.2.2. Build Parameters.".format(fingerprint))
412
Daniel Normand5fe8622020-01-08 17:01:11 -0800413 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800414 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800415 try:
416 fingerprint = self.CalculatePartitionFingerprint(partition)
417 check_fingerprint(fingerprint)
418 self._partition_fingerprints[partition] = fingerprint
419 except ExternalError:
420 continue
421 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800422 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800423 # need a fingerprint when creating the image.
424 self._partition_fingerprints[
425 "system_other"] = self._partition_fingerprints["system"]
426
Tao Bao1c320f82019-10-04 23:25:12 -0700427 # These two should be computed only after setting self._oem_props.
428 self._device = self.GetOemProperty("ro.product.device")
429 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800430 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700431
432 @property
433 def is_ab(self):
434 return self._is_ab
435
436 @property
437 def device(self):
438 return self._device
439
440 @property
441 def fingerprint(self):
442 return self._fingerprint
443
444 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400445 def is_vabc(self):
446 vendor_prop = self.info_dict.get("vendor.build.prop")
447 vabc_enabled = vendor_prop and \
448 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
449 return vabc_enabled
450
451 @property
Kelvin Zhang10eac082021-06-10 14:32:19 -0400452 def vendor_suppressed_vabc(self):
453 vendor_prop = self.info_dict.get("vendor.build.prop")
454 vabc_suppressed = vendor_prop and \
455 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
456 return vabc_suppressed and vabc_suppressed.lower() == "true"
457
458 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700459 def oem_props(self):
460 return self._oem_props
461
462 def __getitem__(self, key):
463 return self.info_dict[key]
464
465 def __setitem__(self, key, value):
466 self.info_dict[key] = value
467
468 def get(self, key, default=None):
469 return self.info_dict.get(key, default)
470
471 def items(self):
472 return self.info_dict.items()
473
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000474 def _GetRawBuildProp(self, prop, partition):
475 prop_file = '{}.build.prop'.format(
476 partition) if partition else 'build.prop'
477 partition_props = self.info_dict.get(prop_file)
478 if not partition_props:
479 return None
480 return partition_props.GetProp(prop)
481
Daniel Normand5fe8622020-01-08 17:01:11 -0800482 def GetPartitionBuildProp(self, prop, partition):
483 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800484
485 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400486 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800487
Daniel Normand5fe8622020-01-08 17:01:11 -0800488 # If provided a partition for this property, only look within that
489 # partition's build.prop.
490 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800491 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800492 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800493 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494
495 prop_val = self._GetRawBuildProp(prop, partition)
496 if prop_val is not None:
497 return prop_val
498 raise ExternalError("couldn't find %s in %s.build.prop" %
499 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800500
Tao Bao1c320f82019-10-04 23:25:12 -0700501 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800502 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700503 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
504 return self._ResolveRoProductBuildProp(prop)
505
Tianjiefdda51d2021-05-05 14:46:35 -0700506 if prop == "ro.build.id":
507 return self._GetBuildId()
508
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000509 prop_val = self._GetRawBuildProp(prop, None)
510 if prop_val is not None:
511 return prop_val
512
513 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700514
515 def _ResolveRoProductBuildProp(self, prop):
516 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000517 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700518 if prop_val:
519 return prop_val
520
Steven Laver8e2086e2020-04-27 16:26:31 -0700521 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000522 source_order_val = self._GetRawBuildProp(
523 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700524 if source_order_val:
525 source_order = source_order_val.split(",")
526 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700527 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700528
529 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700530 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700531 raise ExternalError(
532 "Invalid ro.product.property_source_order '{}'".format(source_order))
533
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000534 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700535 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000536 "ro.product", "ro.product.{}".format(source_partition), 1)
537 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700538 if prop_val:
539 return prop_val
540
541 raise ExternalError("couldn't resolve {}".format(prop))
542
Steven Laver8e2086e2020-04-27 16:26:31 -0700543 def _GetRoProductPropsDefaultSourceOrder(self):
544 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
545 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000546 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700547 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000548 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700549 if android_version == "10":
550 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
551 # NOTE: float() conversion of android_version will have rounding error.
552 # We are checking for "9" or less, and using "< 10" is well outside of
553 # possible floating point rounding.
554 try:
555 android_version_val = float(android_version)
556 except ValueError:
557 android_version_val = 0
558 if android_version_val < 10:
559 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
560 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
561
Tianjieb37c5be2020-10-15 21:27:10 -0700562 def _GetPlatformVersion(self):
563 version_sdk = self.GetBuildProp("ro.build.version.sdk")
564 # init code switches to version_release_or_codename (see b/158483506). After
565 # API finalization, release_or_codename will be the same as release. This
566 # is the best effort to support pre-S dev stage builds.
567 if int(version_sdk) >= 30:
568 try:
569 return self.GetBuildProp("ro.build.version.release_or_codename")
570 except ExternalError:
571 logger.warning('Failed to find ro.build.version.release_or_codename')
572
573 return self.GetBuildProp("ro.build.version.release")
574
Tianjiefdda51d2021-05-05 14:46:35 -0700575 def _GetBuildId(self):
576 build_id = self._GetRawBuildProp("ro.build.id", None)
577 if build_id:
578 return build_id
579
580 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
581 if not legacy_build_id:
582 raise ExternalError("Couldn't find build id in property file")
583
584 if self.use_legacy_id:
585 return legacy_build_id
586
587 # Append the top 8 chars of vbmeta digest to the existing build id. The
588 # logic needs to match the one in init, so that OTA can deliver correctly.
589 avb_enable = self.info_dict.get("avb_enable") == "true"
590 if not avb_enable:
591 raise ExternalError("AVB isn't enabled when using legacy build id")
592
593 vbmeta_digest = self.info_dict.get("vbmeta_digest")
594 if not vbmeta_digest:
595 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
596 " id")
597 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
598 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
599
600 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
601 return legacy_build_id + '.' + digest_prefix
602
Tianjieb37c5be2020-10-15 21:27:10 -0700603 def _GetPartitionPlatformVersion(self, partition):
604 try:
605 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
606 partition)
607 except ExternalError:
608 return self.GetPartitionBuildProp("ro.build.version.release",
609 partition)
610
Tao Bao1c320f82019-10-04 23:25:12 -0700611 def GetOemProperty(self, key):
612 if self.oem_props is not None and key in self.oem_props:
613 return self.oem_dicts[0][key]
614 return self.GetBuildProp(key)
615
Daniel Normand5fe8622020-01-08 17:01:11 -0800616 def GetPartitionFingerprint(self, partition):
617 return self._partition_fingerprints.get(partition, None)
618
619 def CalculatePartitionFingerprint(self, partition):
620 try:
621 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
622 except ExternalError:
623 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
624 self.GetPartitionBuildProp("ro.product.brand", partition),
625 self.GetPartitionBuildProp("ro.product.name", partition),
626 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700627 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800628 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400629 self.GetPartitionBuildProp(
630 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800631 self.GetPartitionBuildProp("ro.build.type", partition),
632 self.GetPartitionBuildProp("ro.build.tags", partition))
633
Tao Bao1c320f82019-10-04 23:25:12 -0700634 def CalculateFingerprint(self):
635 if self.oem_props is None:
636 try:
637 return self.GetBuildProp("ro.build.fingerprint")
638 except ExternalError:
639 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
640 self.GetBuildProp("ro.product.brand"),
641 self.GetBuildProp("ro.product.name"),
642 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700643 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700644 self.GetBuildProp("ro.build.id"),
645 self.GetBuildProp("ro.build.version.incremental"),
646 self.GetBuildProp("ro.build.type"),
647 self.GetBuildProp("ro.build.tags"))
648 return "%s/%s/%s:%s" % (
649 self.GetOemProperty("ro.product.brand"),
650 self.GetOemProperty("ro.product.name"),
651 self.GetOemProperty("ro.product.device"),
652 self.GetBuildProp("ro.build.thumbprint"))
653
654 def WriteMountOemScript(self, script):
655 assert self.oem_props is not None
656 recovery_mount_options = self.info_dict.get("recovery_mount_options")
657 script.Mount("/oem", recovery_mount_options)
658
659 def WriteDeviceAssertions(self, script, oem_no_mount):
660 # Read the property directly if not using OEM properties.
661 if not self.oem_props:
662 script.AssertDevice(self.device)
663 return
664
665 # Otherwise assert OEM properties.
666 if not self.oem_dicts:
667 raise ExternalError(
668 "No OEM file provided to answer expected assertions")
669
670 for prop in self.oem_props.split():
671 values = []
672 for oem_dict in self.oem_dicts:
673 if prop in oem_dict:
674 values.append(oem_dict[prop])
675 if not values:
676 raise ExternalError(
677 "The OEM file is missing the property %s" % (prop,))
678 script.AssertOemProperty(prop, values, oem_no_mount)
679
680
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000681def ReadFromInputFile(input_file, fn):
682 """Reads the contents of fn from input zipfile or directory."""
683 if isinstance(input_file, zipfile.ZipFile):
684 return input_file.read(fn).decode()
685 else:
686 path = os.path.join(input_file, *fn.split("/"))
687 try:
688 with open(path) as f:
689 return f.read()
690 except IOError as e:
691 if e.errno == errno.ENOENT:
692 raise KeyError(fn)
693
694
Yifan Hong10482a22021-01-07 14:38:41 -0800695def ExtractFromInputFile(input_file, fn):
696 """Extracts the contents of fn from input zipfile or directory into a file."""
697 if isinstance(input_file, zipfile.ZipFile):
698 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500699 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800700 f.write(input_file.read(fn))
701 return tmp_file
702 else:
703 file = os.path.join(input_file, *fn.split("/"))
704 if not os.path.exists(file):
705 raise KeyError(fn)
706 return file
707
Kelvin Zhang563750f2021-04-28 12:46:17 -0400708
jiajia tangf3f842b2021-03-17 21:49:44 +0800709class RamdiskFormat(object):
710 LZ4 = 1
711 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800712
Kelvin Zhang563750f2021-04-28 12:46:17 -0400713
jiajia tang836f76b2021-04-02 14:48:26 +0800714def _GetRamdiskFormat(info_dict):
715 if info_dict.get('lz4_ramdisks') == 'true':
716 ramdisk_format = RamdiskFormat.LZ4
717 else:
718 ramdisk_format = RamdiskFormat.GZ
719 return ramdisk_format
720
Kelvin Zhang563750f2021-04-28 12:46:17 -0400721
Tao Bao410ad8b2018-08-24 12:08:38 -0700722def LoadInfoDict(input_file, repacking=False):
723 """Loads the key/value pairs from the given input target_files.
724
Tianjiea85bdf02020-07-29 11:56:19 -0700725 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700726 checks and returns the parsed key/value pairs for to the given build. It's
727 usually called early when working on input target_files files, e.g. when
728 generating OTAs, or signing builds. Note that the function may be called
729 against an old target_files file (i.e. from past dessert releases). So the
730 property parsing needs to be backward compatible.
731
732 In a `META/misc_info.txt`, a few properties are stored as links to the files
733 in the PRODUCT_OUT directory. It works fine with the build system. However,
734 they are no longer available when (re)generating images from target_files zip.
735 When `repacking` is True, redirect these properties to the actual files in the
736 unzipped directory.
737
738 Args:
739 input_file: The input target_files file, which could be an open
740 zipfile.ZipFile instance, or a str for the dir that contains the files
741 unzipped from a target_files file.
742 repacking: Whether it's trying repack an target_files file after loading the
743 info dict (default: False). If so, it will rewrite a few loaded
744 properties (e.g. selinux_fc, root_dir) to point to the actual files in
745 target_files file. When doing repacking, `input_file` must be a dir.
746
747 Returns:
748 A dict that contains the parsed key/value pairs.
749
750 Raises:
751 AssertionError: On invalid input arguments.
752 ValueError: On malformed input values.
753 """
754 if repacking:
755 assert isinstance(input_file, str), \
756 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700757
Doug Zongkerc9253822014-02-04 12:17:58 -0800758 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000759 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800760
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700761 try:
Michael Runge6e836112014-04-15 17:40:21 -0700762 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700763 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700764 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700765
Tao Bao410ad8b2018-08-24 12:08:38 -0700766 if "recovery_api_version" not in d:
767 raise ValueError("Failed to find 'recovery_api_version'")
768 if "fstab_version" not in d:
769 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800770
Tao Bao410ad8b2018-08-24 12:08:38 -0700771 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700772 # "selinux_fc" properties should point to the file_contexts files
773 # (file_contexts.bin) under META/.
774 for key in d:
775 if key.endswith("selinux_fc"):
776 fc_basename = os.path.basename(d[key])
777 fc_config = os.path.join(input_file, "META", fc_basename)
778 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700779
Daniel Norman72c626f2019-05-13 15:58:14 -0700780 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700781
Tom Cherryd14b8952018-08-09 14:26:00 -0700782 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700783 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700784 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700785 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700786
David Anderson0ec64ac2019-12-06 12:21:18 -0800787 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700788 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700789 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800790 key_name = part_name + "_base_fs_file"
791 if key_name not in d:
792 continue
793 basename = os.path.basename(d[key_name])
794 base_fs_file = os.path.join(input_file, "META", basename)
795 if os.path.exists(base_fs_file):
796 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700797 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700798 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800799 "Failed to find %s base fs file: %s", part_name, base_fs_file)
800 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700801
Doug Zongker37974732010-09-16 17:44:38 -0700802 def makeint(key):
803 if key in d:
804 d[key] = int(d[key], 0)
805
806 makeint("recovery_api_version")
807 makeint("blocksize")
808 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700809 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700810 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700811 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700812 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800813 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700814
Steve Muckle903a1ca2020-05-07 17:32:10 -0700815 boot_images = "boot.img"
816 if "boot_images" in d:
817 boot_images = d["boot_images"]
818 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400819 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700820
Tao Bao765668f2019-10-04 22:03:00 -0700821 # Load recovery fstab if applicable.
822 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800823 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800824
Tianjie Xu861f4132018-09-12 11:49:33 -0700825 # Tries to load the build props for all partitions with care_map, including
826 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800827 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800828 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000829 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800830 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700831 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800832
Tao Bao3ed35d32019-10-07 20:48:48 -0700833 # Set up the salt (based on fingerprint) that will be used when adding AVB
834 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800835 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700836 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800837 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800838 fingerprint = build_info.GetPartitionFingerprint(partition)
839 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400840 d["avb_{}_salt".format(partition)] = sha256(
841 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700842
843 # Set the vbmeta digest if exists
844 try:
845 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
846 except KeyError:
847 pass
848
Kelvin Zhang39aea442020-08-17 11:04:25 -0400849 try:
850 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
851 except KeyError:
852 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700853 return d
854
Tao Baod1de6f32017-03-01 16:38:48 -0800855
Daniel Norman4cc9df62019-07-18 10:11:07 -0700856def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900857 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700858 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900859
Daniel Norman4cc9df62019-07-18 10:11:07 -0700860
861def LoadDictionaryFromFile(file_path):
862 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900863 return LoadDictionaryFromLines(lines)
864
865
Michael Runge6e836112014-04-15 17:40:21 -0700866def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700867 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700868 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700869 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700870 if not line or line.startswith("#"):
871 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700872 if "=" in line:
873 name, value = line.split("=", 1)
874 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700875 return d
876
Tao Baod1de6f32017-03-01 16:38:48 -0800877
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000878class PartitionBuildProps(object):
879 """The class holds the build prop of a particular partition.
880
881 This class loads the build.prop and holds the build properties for a given
882 partition. It also partially recognizes the 'import' statement in the
883 build.prop; and calculates alternative values of some specific build
884 properties during runtime.
885
886 Attributes:
887 input_file: a zipped target-file or an unzipped target-file directory.
888 partition: name of the partition.
889 props_allow_override: a list of build properties to search for the
890 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000891 build_props: a dict of build properties for the given partition.
892 prop_overrides: a set of props that are overridden by import.
893 placeholder_values: A dict of runtime variables' values to replace the
894 placeholders in the build.prop file. We expect exactly one value for
895 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800896 ramdisk_format: If name is "boot", the format of ramdisk inside the
897 boot image. Otherwise, its value is ignored.
898 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000899 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400900
Tianjie Xu9afb2212020-05-10 21:48:15 +0000901 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000902 self.input_file = input_file
903 self.partition = name
904 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000905 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000906 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000907 self.prop_overrides = set()
908 self.placeholder_values = {}
909 if placeholder_values:
910 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000911
912 @staticmethod
913 def FromDictionary(name, build_props):
914 """Constructs an instance from a build prop dictionary."""
915
916 props = PartitionBuildProps("unknown", name)
917 props.build_props = build_props.copy()
918 return props
919
920 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800921 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000922 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800923
924 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400925 data = PartitionBuildProps._ReadBootPropFile(
926 input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800927 else:
928 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
929
930 props = PartitionBuildProps(input_file, name, placeholder_values)
931 props._LoadBuildProp(data)
932 return props
933
934 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800935 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800936 """
937 Read build.prop for boot image from input_file.
938 Return empty string if not found.
939 """
940 try:
941 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
942 except KeyError:
943 logger.warning('Failed to read IMAGES/boot.img')
944 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800945 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800946 if prop_file is None:
947 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500948 with open(prop_file, "r") as f:
949 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800950
951 @staticmethod
952 def _ReadPartitionPropFile(input_file, name):
953 """
954 Read build.prop for name from input_file.
955 Return empty string if not found.
956 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000957 data = ''
958 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
959 '{}/build.prop'.format(name.upper())]:
960 try:
961 data = ReadFromInputFile(input_file, prop_file)
962 break
963 except KeyError:
964 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800965 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000966
Yifan Hong125d0b62020-09-24 17:07:03 -0700967 @staticmethod
968 def FromBuildPropFile(name, build_prop_file):
969 """Constructs an instance from a build prop file."""
970
971 props = PartitionBuildProps("unknown", name)
972 with open(build_prop_file) as f:
973 props._LoadBuildProp(f.read())
974 return props
975
Tianjie Xu9afb2212020-05-10 21:48:15 +0000976 def _LoadBuildProp(self, data):
977 for line in data.split('\n'):
978 line = line.strip()
979 if not line or line.startswith("#"):
980 continue
981 if line.startswith("import"):
982 overrides = self._ImportParser(line)
983 duplicates = self.prop_overrides.intersection(overrides.keys())
984 if duplicates:
985 raise ValueError('prop {} is overridden multiple times'.format(
986 ','.join(duplicates)))
987 self.prop_overrides = self.prop_overrides.union(overrides.keys())
988 self.build_props.update(overrides)
989 elif "=" in line:
990 name, value = line.split("=", 1)
991 if name in self.prop_overrides:
992 raise ValueError('prop {} is set again after overridden by import '
993 'statement'.format(name))
994 self.build_props[name] = value
995
996 def _ImportParser(self, line):
997 """Parses the build prop in a given import statement."""
998
999 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001000 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001001 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001002
1003 if len(tokens) == 3:
1004 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1005 return {}
1006
Tianjie Xu9afb2212020-05-10 21:48:15 +00001007 import_path = tokens[1]
1008 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1009 raise ValueError('Unrecognized import path {}'.format(line))
1010
1011 # We only recognize a subset of import statement that the init process
1012 # supports. And we can loose the restriction based on how the dynamic
1013 # fingerprint is used in practice. The placeholder format should be
1014 # ${placeholder}, and its value should be provided by the caller through
1015 # the placeholder_values.
1016 for prop, value in self.placeholder_values.items():
1017 prop_place_holder = '${{{}}}'.format(prop)
1018 if prop_place_holder in import_path:
1019 import_path = import_path.replace(prop_place_holder, value)
1020 if '$' in import_path:
1021 logger.info('Unresolved place holder in import path %s', import_path)
1022 return {}
1023
1024 import_path = import_path.replace('/{}'.format(self.partition),
1025 self.partition.upper())
1026 logger.info('Parsing build props override from %s', import_path)
1027
1028 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1029 d = LoadDictionaryFromLines(lines)
1030 return {key: val for key, val in d.items()
1031 if key in self.props_allow_override}
1032
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001033 def GetProp(self, prop):
1034 return self.build_props.get(prop)
1035
1036
Tianjie Xucfa86222016-03-07 16:31:19 -08001037def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1038 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001039 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001040 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001041 self.mount_point = mount_point
1042 self.fs_type = fs_type
1043 self.device = device
1044 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001045 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001046 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001047
1048 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001049 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001050 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001051 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001052 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001053
Tao Baod1de6f32017-03-01 16:38:48 -08001054 assert fstab_version == 2
1055
1056 d = {}
1057 for line in data.split("\n"):
1058 line = line.strip()
1059 if not line or line.startswith("#"):
1060 continue
1061
1062 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1063 pieces = line.split()
1064 if len(pieces) != 5:
1065 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1066
1067 # Ignore entries that are managed by vold.
1068 options = pieces[4]
1069 if "voldmanaged=" in options:
1070 continue
1071
1072 # It's a good line, parse it.
1073 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001074 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001075 options = options.split(",")
1076 for i in options:
1077 if i.startswith("length="):
1078 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001079 elif i == "slotselect":
1080 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001081 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001082 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001083 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001084
Tao Baod1de6f32017-03-01 16:38:48 -08001085 mount_flags = pieces[3]
1086 # Honor the SELinux context if present.
1087 context = None
1088 for i in mount_flags.split(","):
1089 if i.startswith("context="):
1090 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001091
Tao Baod1de6f32017-03-01 16:38:48 -08001092 mount_point = pieces[1]
1093 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001094 device=pieces[0], length=length, context=context,
1095 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001096
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001097 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001098 # system. Other areas assume system is always at "/system" so point /system
1099 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001100 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001101 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001102 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001103 return d
1104
1105
Tao Bao765668f2019-10-04 22:03:00 -07001106def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1107 """Finds the path to recovery fstab and loads its contents."""
1108 # recovery fstab is only meaningful when installing an update via recovery
1109 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001110 if info_dict.get('ab_update') == 'true' and \
1111 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001112 return None
1113
1114 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1115 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1116 # cases, since it may load the info_dict from an old build (e.g. when
1117 # generating incremental OTAs from that build).
1118 system_root_image = info_dict.get('system_root_image') == 'true'
1119 if info_dict.get('no_recovery') != 'true':
1120 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1121 if isinstance(input_file, zipfile.ZipFile):
1122 if recovery_fstab_path not in input_file.namelist():
1123 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1124 else:
1125 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1126 if not os.path.exists(path):
1127 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1128 return LoadRecoveryFSTab(
1129 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1130 system_root_image)
1131
1132 if info_dict.get('recovery_as_boot') == 'true':
1133 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1134 if isinstance(input_file, zipfile.ZipFile):
1135 if recovery_fstab_path not in input_file.namelist():
1136 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1137 else:
1138 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1139 if not os.path.exists(path):
1140 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1141 return LoadRecoveryFSTab(
1142 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1143 system_root_image)
1144
1145 return None
1146
1147
Doug Zongker37974732010-09-16 17:44:38 -07001148def DumpInfoDict(d):
1149 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001150 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001151
Dan Albert8b72aef2015-03-23 19:13:21 -07001152
Daniel Norman55417142019-11-25 16:04:36 -08001153def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001154 """Merges dynamic partition info variables.
1155
1156 Args:
1157 framework_dict: The dictionary of dynamic partition info variables from the
1158 partial framework target files.
1159 vendor_dict: The dictionary of dynamic partition info variables from the
1160 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001161
1162 Returns:
1163 The merged dynamic partition info dictionary.
1164 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001165
1166 def uniq_concat(a, b):
1167 combined = set(a.split(" "))
1168 combined.update(set(b.split(" ")))
1169 combined = [item.strip() for item in combined if item.strip()]
1170 return " ".join(sorted(combined))
1171
1172 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001173 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001174 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1175
1176 merged_dict = {"use_dynamic_partitions": "true"}
1177
1178 merged_dict["dynamic_partition_list"] = uniq_concat(
1179 framework_dict.get("dynamic_partition_list", ""),
1180 vendor_dict.get("dynamic_partition_list", ""))
1181
1182 # Super block devices are defined by the vendor dict.
1183 if "super_block_devices" in vendor_dict:
1184 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1185 for block_device in merged_dict["super_block_devices"].split(" "):
1186 key = "super_%s_device_size" % block_device
1187 if key not in vendor_dict:
1188 raise ValueError("Vendor dict does not contain required key %s." % key)
1189 merged_dict[key] = vendor_dict[key]
1190
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001191 # Partition groups and group sizes are defined by the vendor dict because
1192 # these values may vary for each board that uses a shared system image.
1193 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001194 for partition_group in merged_dict["super_partition_groups"].split(" "):
1195 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001196 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001197 if key not in vendor_dict:
1198 raise ValueError("Vendor dict does not contain required key %s." % key)
1199 merged_dict[key] = vendor_dict[key]
1200
1201 # Set the partition group's partition list using a concatenation of the
1202 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001203 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001204 merged_dict[key] = uniq_concat(
1205 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301206
Daniel Normanb0c75912020-09-24 14:30:21 -07001207 # Various other flags should be copied from the vendor dict, if defined.
1208 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1209 "super_metadata_device", "super_partition_error_limit",
1210 "super_partition_size"):
1211 if key in vendor_dict.keys():
1212 merged_dict[key] = vendor_dict[key]
1213
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001214 return merged_dict
1215
1216
Daniel Norman21c34f72020-11-11 17:25:50 -08001217def PartitionMapFromTargetFiles(target_files_dir):
1218 """Builds a map from partition -> path within an extracted target files directory."""
1219 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1220 possible_subdirs = {
1221 "system": ["SYSTEM"],
1222 "vendor": ["VENDOR", "SYSTEM/vendor"],
1223 "product": ["PRODUCT", "SYSTEM/product"],
1224 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1225 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1226 "vendor_dlkm": [
1227 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1228 ],
1229 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1230 }
1231 partition_map = {}
1232 for partition, subdirs in possible_subdirs.items():
1233 for subdir in subdirs:
1234 if os.path.exists(os.path.join(target_files_dir, subdir)):
1235 partition_map[partition] = subdir
1236 break
1237 return partition_map
1238
1239
Daniel Normand3351562020-10-29 12:33:11 -07001240def SharedUidPartitionViolations(uid_dict, partition_groups):
1241 """Checks for APK sharedUserIds that cross partition group boundaries.
1242
1243 This uses a single or merged build's shareduid_violation_modules.json
1244 output file, as generated by find_shareduid_violation.py or
1245 core/tasks/find-shareduid-violation.mk.
1246
1247 An error is defined as a sharedUserId that is found in a set of partitions
1248 that span more than one partition group.
1249
1250 Args:
1251 uid_dict: A dictionary created by using the standard json module to read a
1252 complete shareduid_violation_modules.json file.
1253 partition_groups: A list of groups, where each group is a list of
1254 partitions.
1255
1256 Returns:
1257 A list of error messages.
1258 """
1259 errors = []
1260 for uid, partitions in uid_dict.items():
1261 found_in_groups = [
1262 group for group in partition_groups
1263 if set(partitions.keys()) & set(group)
1264 ]
1265 if len(found_in_groups) > 1:
1266 errors.append(
1267 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1268 % (uid, ",".join(sorted(partitions.keys()))))
1269 return errors
1270
1271
Daniel Norman21c34f72020-11-11 17:25:50 -08001272def RunHostInitVerifier(product_out, partition_map):
1273 """Runs host_init_verifier on the init rc files within partitions.
1274
1275 host_init_verifier searches the etc/init path within each partition.
1276
1277 Args:
1278 product_out: PRODUCT_OUT directory, containing partition directories.
1279 partition_map: A map of partition name -> relative path within product_out.
1280 """
1281 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1282 cmd = ["host_init_verifier"]
1283 for partition, path in partition_map.items():
1284 if partition not in allowed_partitions:
1285 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1286 partition)
1287 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1288 # Add --property-contexts if the file exists on the partition.
1289 property_contexts = "%s_property_contexts" % (
1290 "plat" if partition == "system" else partition)
1291 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1292 property_contexts)
1293 if os.path.exists(property_contexts_path):
1294 cmd.append("--property-contexts=%s" % property_contexts_path)
1295 # Add the passwd file if the file exists on the partition.
1296 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1297 if os.path.exists(passwd_path):
1298 cmd.extend(["-p", passwd_path])
1299 return RunAndCheckOutput(cmd)
1300
1301
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001302def AppendAVBSigningArgs(cmd, partition):
1303 """Append signing arguments for avbtool."""
1304 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1305 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001306 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1307 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1308 if os.path.exists(new_key_path):
1309 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001310 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1311 if key_path and algorithm:
1312 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001313 avb_salt = OPTIONS.info_dict.get("avb_salt")
1314 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001315 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001316 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001317
1318
Tao Bao765668f2019-10-04 22:03:00 -07001319def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001320 """Returns the VBMeta arguments for partition.
1321
1322 It sets up the VBMeta argument by including the partition descriptor from the
1323 given 'image', or by configuring the partition as a chained partition.
1324
1325 Args:
1326 partition: The name of the partition (e.g. "system").
1327 image: The path to the partition image.
1328 info_dict: A dict returned by common.LoadInfoDict(). Will use
1329 OPTIONS.info_dict if None has been given.
1330
1331 Returns:
1332 A list of VBMeta arguments.
1333 """
1334 if info_dict is None:
1335 info_dict = OPTIONS.info_dict
1336
1337 # Check if chain partition is used.
1338 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001339 if not key_path:
1340 return ["--include_descriptors_from_image", image]
1341
1342 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1343 # into vbmeta.img. The recovery image will be configured on an independent
1344 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1345 # See details at
1346 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001347 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001348 return []
1349
1350 # Otherwise chain the partition into vbmeta.
1351 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1352 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001353
1354
Tao Bao02a08592018-07-22 12:40:45 -07001355def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1356 """Constructs and returns the arg to build or verify a chained partition.
1357
1358 Args:
1359 partition: The partition name.
1360 info_dict: The info dict to look up the key info and rollback index
1361 location.
1362 key: The key to be used for building or verifying the partition. Defaults to
1363 the key listed in info_dict.
1364
1365 Returns:
1366 A string of form "partition:rollback_index_location:key" that can be used to
1367 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001368 """
1369 if key is None:
1370 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001371 if key and not os.path.exists(key) and OPTIONS.search_path:
1372 new_key_path = os.path.join(OPTIONS.search_path, key)
1373 if os.path.exists(new_key_path):
1374 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001375 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001376 rollback_index_location = info_dict[
1377 "avb_" + partition + "_rollback_index_location"]
1378 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1379
1380
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001381def AppendGkiSigningArgs(cmd):
1382 """Append GKI signing arguments for mkbootimg."""
1383 # e.g., --gki_signing_key path/to/signing_key
1384 # --gki_signing_algorithm SHA256_RSA4096"
1385
1386 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1387 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1388 if not key_path:
1389 return
1390
1391 if not os.path.exists(key_path) and OPTIONS.search_path:
1392 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1393 if os.path.exists(new_key_path):
1394 key_path = new_key_path
1395
1396 # Checks key_path exists, before appending --gki_signing_* args.
1397 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001398 raise ExternalError(
1399 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001400
1401 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1402 if key_path and algorithm:
1403 cmd.extend(["--gki_signing_key", key_path,
1404 "--gki_signing_algorithm", algorithm])
1405
1406 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1407 if signature_args:
1408 cmd.extend(["--gki_signing_signature_args", signature_args])
1409
1410
Daniel Norman276f0622019-07-26 14:13:51 -07001411def BuildVBMeta(image_path, partitions, name, needed_partitions):
1412 """Creates a VBMeta image.
1413
1414 It generates the requested VBMeta image. The requested image could be for
1415 top-level or chained VBMeta image, which is determined based on the name.
1416
1417 Args:
1418 image_path: The output path for the new VBMeta image.
1419 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001420 values. Only valid partition names are accepted, as partitions listed
1421 in common.AVB_PARTITIONS and custom partitions listed in
1422 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001423 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1424 needed_partitions: Partitions whose descriptors should be included into the
1425 generated VBMeta image.
1426
1427 Raises:
1428 AssertionError: On invalid input args.
1429 """
1430 avbtool = OPTIONS.info_dict["avb_avbtool"]
1431 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1432 AppendAVBSigningArgs(cmd, name)
1433
Hongguang Chenf23364d2020-04-27 18:36:36 -07001434 custom_partitions = OPTIONS.info_dict.get(
1435 "avb_custom_images_partition_list", "").strip().split()
1436
Daniel Norman276f0622019-07-26 14:13:51 -07001437 for partition, path in partitions.items():
1438 if partition not in needed_partitions:
1439 continue
1440 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001441 partition in AVB_VBMETA_PARTITIONS or
1442 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001443 'Unknown partition: {}'.format(partition)
1444 assert os.path.exists(path), \
1445 'Failed to find {} for {}'.format(path, partition)
1446 cmd.extend(GetAvbPartitionArg(partition, path))
1447
1448 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1449 if args and args.strip():
1450 split_args = shlex.split(args)
1451 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001452 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001453 # as a path relative to source tree, which may not be available at the
1454 # same location when running this script (we have the input target_files
1455 # zip only). For such cases, we additionally scan other locations (e.g.
1456 # IMAGES/, RADIO/, etc) before bailing out.
1457 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001458 chained_image = split_args[index + 1]
1459 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001460 continue
1461 found = False
1462 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1463 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001464 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001465 if os.path.exists(alt_path):
1466 split_args[index + 1] = alt_path
1467 found = True
1468 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001469 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001470 cmd.extend(split_args)
1471
1472 RunAndCheckOutput(cmd)
1473
1474
jiajia tang836f76b2021-04-02 14:48:26 +08001475def _MakeRamdisk(sourcedir, fs_config_file=None,
1476 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001477 ramdisk_img = tempfile.NamedTemporaryFile()
1478
1479 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1480 cmd = ["mkbootfs", "-f", fs_config_file,
1481 os.path.join(sourcedir, "RAMDISK")]
1482 else:
1483 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1484 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001485 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001486 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001487 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001488 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001489 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001490 else:
1491 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001492
1493 p2.wait()
1494 p1.wait()
1495 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001496 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001497
1498 return ramdisk_img
1499
1500
Steve Muckle9793cf62020-04-08 18:27:00 -07001501def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001502 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001503 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001504
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001505 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001506 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1507 we are building a two-step special image (i.e. building a recovery image to
1508 be loaded into /boot in two-step OTAs).
1509
1510 Return the image data, or None if sourcedir does not appear to contains files
1511 for building the requested image.
1512 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001513
Yifan Hong63c5ca12020-10-08 11:54:02 -07001514 if info_dict is None:
1515 info_dict = OPTIONS.info_dict
1516
Steve Muckle9793cf62020-04-08 18:27:00 -07001517 # "boot" or "recovery", without extension.
1518 partition_name = os.path.basename(sourcedir).lower()
1519
Yifan Hong63c5ca12020-10-08 11:54:02 -07001520 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001521 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001522 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1523 logger.info("Excluded kernel binary from recovery image.")
1524 else:
1525 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001526 else:
1527 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001528 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001529 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001530 return None
1531
1532 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001533 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001534
Doug Zongkereef39442009-04-02 12:14:19 -07001535 img = tempfile.NamedTemporaryFile()
1536
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001537 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001538 ramdisk_format = _GetRamdiskFormat(info_dict)
1539 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1540 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001541
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001542 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1543 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1544
Yifan Hong63c5ca12020-10-08 11:54:02 -07001545 cmd = [mkbootimg]
1546 if kernel:
1547 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001548
Benoit Fradina45a8682014-07-14 21:00:43 +02001549 fn = os.path.join(sourcedir, "second")
1550 if os.access(fn, os.F_OK):
1551 cmd.append("--second")
1552 cmd.append(fn)
1553
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001554 fn = os.path.join(sourcedir, "dtb")
1555 if os.access(fn, os.F_OK):
1556 cmd.append("--dtb")
1557 cmd.append(fn)
1558
Doug Zongker171f1cd2009-06-15 22:36:37 -07001559 fn = os.path.join(sourcedir, "cmdline")
1560 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001561 cmd.append("--cmdline")
1562 cmd.append(open(fn).read().rstrip("\n"))
1563
1564 fn = os.path.join(sourcedir, "base")
1565 if os.access(fn, os.F_OK):
1566 cmd.append("--base")
1567 cmd.append(open(fn).read().rstrip("\n"))
1568
Ying Wang4de6b5b2010-08-25 14:29:34 -07001569 fn = os.path.join(sourcedir, "pagesize")
1570 if os.access(fn, os.F_OK):
1571 cmd.append("--pagesize")
1572 cmd.append(open(fn).read().rstrip("\n"))
1573
Steve Mucklef84668e2020-03-16 19:13:46 -07001574 if partition_name == "recovery":
1575 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301576 if not args:
1577 # Fall back to "mkbootimg_args" for recovery image
1578 # in case "recovery_mkbootimg_args" is not set.
1579 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001580 else:
1581 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001582 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001583 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001584
Tao Bao76def242017-11-21 09:25:31 -08001585 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001586 if args and args.strip():
1587 cmd.extend(shlex.split(args))
1588
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001589 if has_ramdisk:
1590 cmd.extend(["--ramdisk", ramdisk_img.name])
1591
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001592 AppendGkiSigningArgs(cmd)
1593
Tao Baod95e9fd2015-03-29 23:07:41 -07001594 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001595 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001596 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001597 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001598 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001599 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001600
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001601 if partition_name == "recovery":
1602 if info_dict.get("include_recovery_dtbo") == "true":
1603 fn = os.path.join(sourcedir, "recovery_dtbo")
1604 cmd.extend(["--recovery_dtbo", fn])
1605 if info_dict.get("include_recovery_acpio") == "true":
1606 fn = os.path.join(sourcedir, "recovery_acpio")
1607 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001608
Tao Bao986ee862018-10-04 15:46:16 -07001609 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001610
Tao Bao76def242017-11-21 09:25:31 -08001611 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001612 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001613 # Hard-code the path as "/boot" for two-step special recovery image (which
1614 # will be loaded into /boot during the two-step OTA).
1615 if two_step_image:
1616 path = "/boot"
1617 else:
Tao Baobf70c312017-07-11 17:27:55 -07001618 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001619 cmd = [OPTIONS.boot_signer_path]
1620 cmd.extend(OPTIONS.boot_signer_args)
1621 cmd.extend([path, img.name,
1622 info_dict["verity_key"] + ".pk8",
1623 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001624 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001625
Tao Baod95e9fd2015-03-29 23:07:41 -07001626 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001627 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001628 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001629 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001630 # We have switched from the prebuilt futility binary to using the tool
1631 # (futility-host) built from the source. Override the setting in the old
1632 # TF.zip.
1633 futility = info_dict["futility"]
1634 if futility.startswith("prebuilts/"):
1635 futility = "futility-host"
1636 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001637 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001638 info_dict["vboot_key"] + ".vbprivk",
1639 info_dict["vboot_subkey"] + ".vbprivk",
1640 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001641 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001642 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001643
Tao Baof3282b42015-04-01 11:21:55 -07001644 # Clean up the temp files.
1645 img_unsigned.close()
1646 img_keyblock.close()
1647
David Zeuthen8fecb282017-12-01 16:24:01 -05001648 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001649 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001650 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001651 if partition_name == "recovery":
1652 part_size = info_dict["recovery_size"]
1653 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001654 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001655 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001656 "--partition_size", str(part_size), "--partition_name",
1657 partition_name]
1658 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001659 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001660 if args and args.strip():
1661 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001662 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001663
1664 img.seek(os.SEEK_SET, 0)
1665 data = img.read()
1666
1667 if has_ramdisk:
1668 ramdisk_img.close()
1669 img.close()
1670
1671 return data
1672
1673
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001674def _SignBootableImage(image_path, prebuilt_name, partition_name,
1675 info_dict=None):
1676 """Performs AVB signing for a prebuilt boot.img.
1677
1678 Args:
1679 image_path: The full path of the image, e.g., /path/to/boot.img.
1680 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1681 boot-5.10.img, recovery.img.
1682 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1683 info_dict: The information dict read from misc_info.txt.
1684 """
1685 if info_dict is None:
1686 info_dict = OPTIONS.info_dict
1687
1688 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1689 if info_dict.get("avb_enable") == "true":
1690 avbtool = info_dict["avb_avbtool"]
1691 if partition_name == "recovery":
1692 part_size = info_dict["recovery_size"]
1693 else:
1694 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1695
1696 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1697 "--partition_size", str(part_size), "--partition_name",
1698 partition_name]
1699 AppendAVBSigningArgs(cmd, partition_name)
1700 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1701 if args and args.strip():
1702 cmd.extend(shlex.split(args))
1703 RunAndCheckOutput(cmd)
1704
1705
Doug Zongkerd5131602012-08-02 14:46:42 -07001706def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001707 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001708 """Return a File object with the desired bootable image.
1709
1710 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1711 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1712 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001713
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001714 if info_dict is None:
1715 info_dict = OPTIONS.info_dict
1716
Doug Zongker55d93282011-01-25 17:03:34 -08001717 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1718 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001719 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001720 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001721
1722 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1723 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001724 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001725 return File.FromLocalFile(name, prebuilt_path)
1726
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001727 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1728 if os.path.exists(prebuilt_path):
1729 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1730 signed_img = MakeTempFile()
1731 shutil.copy(prebuilt_path, signed_img)
1732 partition_name = tree_subdir.lower()
1733 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1734 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001735
Bowgo Tsaicf9ead82021-05-20 00:14:42 +08001736 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001737
1738 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001739 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1740 # for recovery.
1741 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1742 prebuilt_name != "boot.img" or
1743 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001744
Doug Zongker6f1d0312014-08-22 08:07:12 -07001745 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001746 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001747 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001748 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001749 if data:
1750 return File(name, data)
1751 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001752
Doug Zongkereef39442009-04-02 12:14:19 -07001753
Steve Mucklee1b10862019-07-10 10:49:37 -07001754def _BuildVendorBootImage(sourcedir, info_dict=None):
1755 """Build a vendor boot image from the specified sourcedir.
1756
1757 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1758 turn them into a vendor boot image.
1759
1760 Return the image data, or None if sourcedir does not appear to contains files
1761 for building the requested image.
1762 """
1763
1764 if info_dict is None:
1765 info_dict = OPTIONS.info_dict
1766
1767 img = tempfile.NamedTemporaryFile()
1768
jiajia tang836f76b2021-04-02 14:48:26 +08001769 ramdisk_format = _GetRamdiskFormat(info_dict)
1770 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001771
1772 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1773 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1774
1775 cmd = [mkbootimg]
1776
1777 fn = os.path.join(sourcedir, "dtb")
1778 if os.access(fn, os.F_OK):
1779 cmd.append("--dtb")
1780 cmd.append(fn)
1781
1782 fn = os.path.join(sourcedir, "vendor_cmdline")
1783 if os.access(fn, os.F_OK):
1784 cmd.append("--vendor_cmdline")
1785 cmd.append(open(fn).read().rstrip("\n"))
1786
1787 fn = os.path.join(sourcedir, "base")
1788 if os.access(fn, os.F_OK):
1789 cmd.append("--base")
1790 cmd.append(open(fn).read().rstrip("\n"))
1791
1792 fn = os.path.join(sourcedir, "pagesize")
1793 if os.access(fn, os.F_OK):
1794 cmd.append("--pagesize")
1795 cmd.append(open(fn).read().rstrip("\n"))
1796
1797 args = info_dict.get("mkbootimg_args")
1798 if args and args.strip():
1799 cmd.extend(shlex.split(args))
1800
1801 args = info_dict.get("mkbootimg_version_args")
1802 if args and args.strip():
1803 cmd.extend(shlex.split(args))
1804
1805 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1806 cmd.extend(["--vendor_boot", img.name])
1807
Devin Moore50509012021-01-13 10:45:04 -08001808 fn = os.path.join(sourcedir, "vendor_bootconfig")
1809 if os.access(fn, os.F_OK):
1810 cmd.append("--vendor_bootconfig")
1811 cmd.append(fn)
1812
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001813 ramdisk_fragment_imgs = []
1814 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1815 if os.access(fn, os.F_OK):
1816 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1817 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001818 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1819 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001820 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001821 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1822 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001823 # Use prebuilt image if found, else create ramdisk from supplied files.
1824 if os.access(fn, os.F_OK):
1825 ramdisk_fragment_pathname = fn
1826 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001827 ramdisk_fragment_root = os.path.join(
1828 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001829 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1830 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001831 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1832 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1833 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1834
Steve Mucklee1b10862019-07-10 10:49:37 -07001835 RunAndCheckOutput(cmd)
1836
1837 # AVB: if enabled, calculate and add hash.
1838 if info_dict.get("avb_enable") == "true":
1839 avbtool = info_dict["avb_avbtool"]
1840 part_size = info_dict["vendor_boot_size"]
1841 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001842 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001843 AppendAVBSigningArgs(cmd, "vendor_boot")
1844 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1845 if args and args.strip():
1846 cmd.extend(shlex.split(args))
1847 RunAndCheckOutput(cmd)
1848
1849 img.seek(os.SEEK_SET, 0)
1850 data = img.read()
1851
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001852 for f in ramdisk_fragment_imgs:
1853 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001854 ramdisk_img.close()
1855 img.close()
1856
1857 return data
1858
1859
1860def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1861 info_dict=None):
1862 """Return a File object with the desired vendor boot image.
1863
1864 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1865 the source files in 'unpack_dir'/'tree_subdir'."""
1866
1867 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1868 if os.path.exists(prebuilt_path):
1869 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1870 return File.FromLocalFile(name, prebuilt_path)
1871
1872 logger.info("building image from target_files %s...", tree_subdir)
1873
1874 if info_dict is None:
1875 info_dict = OPTIONS.info_dict
1876
Kelvin Zhang0876c412020-06-23 15:06:58 -04001877 data = _BuildVendorBootImage(
1878 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001879 if data:
1880 return File(name, data)
1881 return None
1882
1883
Narayan Kamatha07bf042017-08-14 14:49:21 +01001884def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001885 """Gunzips the given gzip compressed file to a given output file."""
1886 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001887 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001888 shutil.copyfileobj(in_file, out_file)
1889
1890
Tao Bao0ff15de2019-03-20 11:26:06 -07001891def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001892 """Unzips the archive to the given directory.
1893
1894 Args:
1895 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001896 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001897 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1898 archvie. Non-matching patterns will be filtered out. If there's no match
1899 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001900 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001901 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001902 if patterns is not None:
1903 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001904 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001905 names = input_zip.namelist()
1906 filtered = [
1907 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1908
1909 # There isn't any matching files. Don't unzip anything.
1910 if not filtered:
1911 return
1912 cmd.extend(filtered)
1913
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001914 RunAndCheckOutput(cmd)
1915
1916
Doug Zongker75f17362009-12-08 13:46:44 -08001917def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001918 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001919
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001920 Args:
1921 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1922 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1923
1924 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1925 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001926
Tao Bao1c830bf2017-12-25 10:43:47 -08001927 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001928 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001929 """
Doug Zongkereef39442009-04-02 12:14:19 -07001930
Tao Bao1c830bf2017-12-25 10:43:47 -08001931 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001932 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1933 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001934 UnzipToDir(m.group(1), tmp, pattern)
1935 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001936 filename = m.group(1)
1937 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001938 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001939
Tao Baodba59ee2018-01-09 13:21:02 -08001940 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001941
1942
Yifan Hong8a66a712019-04-04 15:37:57 -07001943def GetUserImage(which, tmpdir, input_zip,
1944 info_dict=None,
1945 allow_shared_blocks=None,
1946 hashtree_info_generator=None,
1947 reset_file_map=False):
1948 """Returns an Image object suitable for passing to BlockImageDiff.
1949
1950 This function loads the specified image from the given path. If the specified
1951 image is sparse, it also performs additional processing for OTA purpose. For
1952 example, it always adds block 0 to clobbered blocks list. It also detects
1953 files that cannot be reconstructed from the block list, for whom we should
1954 avoid applying imgdiff.
1955
1956 Args:
1957 which: The partition name.
1958 tmpdir: The directory that contains the prebuilt image and block map file.
1959 input_zip: The target-files ZIP archive.
1960 info_dict: The dict to be looked up for relevant info.
1961 allow_shared_blocks: If image is sparse, whether having shared blocks is
1962 allowed. If none, it is looked up from info_dict.
1963 hashtree_info_generator: If present and image is sparse, generates the
1964 hashtree_info for this sparse image.
1965 reset_file_map: If true and image is sparse, reset file map before returning
1966 the image.
1967 Returns:
1968 A Image object. If it is a sparse image and reset_file_map is False, the
1969 image will have file_map info loaded.
1970 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001971 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001972 info_dict = LoadInfoDict(input_zip)
1973
1974 is_sparse = info_dict.get("extfs_sparse_flag")
1975
1976 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1977 # shared blocks (i.e. some blocks will show up in multiple files' block
1978 # list). We can only allocate such shared blocks to the first "owner", and
1979 # disable imgdiff for all later occurrences.
1980 if allow_shared_blocks is None:
1981 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1982
1983 if is_sparse:
1984 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1985 hashtree_info_generator)
1986 if reset_file_map:
1987 img.ResetFileMap()
1988 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001989 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001990
1991
1992def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1993 """Returns a Image object suitable for passing to BlockImageDiff.
1994
1995 This function loads the specified non-sparse image from the given path.
1996
1997 Args:
1998 which: The partition name.
1999 tmpdir: The directory that contains the prebuilt image and block map file.
2000 Returns:
2001 A Image object.
2002 """
2003 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2004 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2005
2006 # The image and map files must have been created prior to calling
2007 # ota_from_target_files.py (since LMP).
2008 assert os.path.exists(path) and os.path.exists(mappath)
2009
Tianjie Xu41976c72019-07-03 13:57:01 -07002010 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2011
Yifan Hong8a66a712019-04-04 15:37:57 -07002012
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002013def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2014 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002015 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2016
2017 This function loads the specified sparse image from the given path, and
2018 performs additional processing for OTA purpose. For example, it always adds
2019 block 0 to clobbered blocks list. It also detects files that cannot be
2020 reconstructed from the block list, for whom we should avoid applying imgdiff.
2021
2022 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002023 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002024 tmpdir: The directory that contains the prebuilt image and block map file.
2025 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002026 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002027 hashtree_info_generator: If present, generates the hashtree_info for this
2028 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002029 Returns:
2030 A SparseImage object, with file_map info loaded.
2031 """
Tao Baoc765cca2018-01-31 17:32:40 -08002032 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2033 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2034
2035 # The image and map files must have been created prior to calling
2036 # ota_from_target_files.py (since LMP).
2037 assert os.path.exists(path) and os.path.exists(mappath)
2038
2039 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2040 # it to clobbered_blocks so that it will be written to the target
2041 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2042 clobbered_blocks = "0"
2043
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002044 image = sparse_img.SparseImage(
2045 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2046 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002047
2048 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2049 # if they contain all zeros. We can't reconstruct such a file from its block
2050 # list. Tag such entries accordingly. (Bug: 65213616)
2051 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002052 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002053 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002054 continue
2055
Tom Cherryd14b8952018-08-09 14:26:00 -07002056 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2057 # filename listed in system.map may contain an additional leading slash
2058 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2059 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002060 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002061 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002062 arcname = entry.lstrip('/')
2063 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002064 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002065 else:
2066 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002067
2068 assert arcname in input_zip.namelist(), \
2069 "Failed to find the ZIP entry for {}".format(entry)
2070
Tao Baoc765cca2018-01-31 17:32:40 -08002071 info = input_zip.getinfo(arcname)
2072 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002073
2074 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002075 # image, check the original block list to determine its completeness. Note
2076 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002077 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002078 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002079
Tao Baoc765cca2018-01-31 17:32:40 -08002080 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2081 ranges.extra['incomplete'] = True
2082
2083 return image
2084
2085
Doug Zongkereef39442009-04-02 12:14:19 -07002086def GetKeyPasswords(keylist):
2087 """Given a list of keys, prompt the user to enter passwords for
2088 those which require them. Return a {key: password} dict. password
2089 will be None if the key has no password."""
2090
Doug Zongker8ce7c252009-05-22 13:34:54 -07002091 no_passwords = []
2092 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002093 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002094 devnull = open("/dev/null", "w+b")
2095 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002096 # We don't need a password for things that aren't really keys.
2097 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002098 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002099 continue
2100
T.R. Fullhart37e10522013-03-18 10:31:26 -07002101 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002102 "-inform", "DER", "-nocrypt"],
2103 stdin=devnull.fileno(),
2104 stdout=devnull.fileno(),
2105 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002106 p.communicate()
2107 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002108 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002109 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002110 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002111 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2112 "-inform", "DER", "-passin", "pass:"],
2113 stdin=devnull.fileno(),
2114 stdout=devnull.fileno(),
2115 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002116 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002117 if p.returncode == 0:
2118 # Encrypted key with empty string as password.
2119 key_passwords[k] = ''
2120 elif stderr.startswith('Error decrypting key'):
2121 # Definitely encrypted key.
2122 # It would have said "Error reading key" if it didn't parse correctly.
2123 need_passwords.append(k)
2124 else:
2125 # Potentially, a type of key that openssl doesn't understand.
2126 # We'll let the routines in signapk.jar handle it.
2127 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002128 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002129
T.R. Fullhart37e10522013-03-18 10:31:26 -07002130 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002131 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002132 return key_passwords
2133
2134
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002135def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002136 """Gets the minSdkVersion declared in the APK.
2137
changho.shin0f125362019-07-08 10:59:00 +09002138 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002139 This can be both a decimal number (API Level) or a codename.
2140
2141 Args:
2142 apk_name: The APK filename.
2143
2144 Returns:
2145 The parsed SDK version string.
2146
2147 Raises:
2148 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002149 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002150 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002151 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002152 stderr=subprocess.PIPE)
2153 stdoutdata, stderrdata = proc.communicate()
2154 if proc.returncode != 0:
2155 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002156 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002157 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002158
Tao Baof47bf0f2018-03-21 23:28:51 -07002159 for line in stdoutdata.split("\n"):
2160 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002161 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2162 if m:
2163 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002164 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002165
2166
2167def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002168 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002169
Tao Baof47bf0f2018-03-21 23:28:51 -07002170 If minSdkVersion is set to a codename, it is translated to a number using the
2171 provided map.
2172
2173 Args:
2174 apk_name: The APK filename.
2175
2176 Returns:
2177 The parsed SDK version number.
2178
2179 Raises:
2180 ExternalError: On failing to get the min SDK version number.
2181 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002182 version = GetMinSdkVersion(apk_name)
2183 try:
2184 return int(version)
2185 except ValueError:
2186 # Not a decimal number. Codename?
2187 if version in codename_to_api_level_map:
2188 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002189 raise ExternalError(
2190 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2191 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002192
2193
2194def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002195 codename_to_api_level_map=None, whole_file=False,
2196 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002197 """Sign the input_name zip/jar/apk, producing output_name. Use the
2198 given key and password (the latter may be None if the key does not
2199 have a password.
2200
Doug Zongker951495f2009-08-14 12:44:19 -07002201 If whole_file is true, use the "-w" option to SignApk to embed a
2202 signature that covers the whole file in the archive comment of the
2203 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002204
2205 min_api_level is the API Level (int) of the oldest platform this file may end
2206 up on. If not specified for an APK, the API Level is obtained by interpreting
2207 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2208
2209 codename_to_api_level_map is needed to translate the codename which may be
2210 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002211
2212 Caller may optionally specify extra args to be passed to SignApk, which
2213 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002214 """
Tao Bao76def242017-11-21 09:25:31 -08002215 if codename_to_api_level_map is None:
2216 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002217 if extra_signapk_args is None:
2218 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002219
Alex Klyubin9667b182015-12-10 13:38:50 -08002220 java_library_path = os.path.join(
2221 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2222
Tao Baoe95540e2016-11-08 12:08:53 -08002223 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2224 ["-Djava.library.path=" + java_library_path,
2225 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002226 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002227 if whole_file:
2228 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002229
2230 min_sdk_version = min_api_level
2231 if min_sdk_version is None:
2232 if not whole_file:
2233 min_sdk_version = GetMinSdkVersionInt(
2234 input_name, codename_to_api_level_map)
2235 if min_sdk_version is not None:
2236 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2237
T.R. Fullhart37e10522013-03-18 10:31:26 -07002238 cmd.extend([key + OPTIONS.public_key_suffix,
2239 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002240 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002241
Tao Bao73dd4f42018-10-04 16:25:33 -07002242 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002243 if password is not None:
2244 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002245 stdoutdata, _ = proc.communicate(password)
2246 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002247 raise ExternalError(
2248 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002249 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002250
Doug Zongkereef39442009-04-02 12:14:19 -07002251
Doug Zongker37974732010-09-16 17:44:38 -07002252def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002253 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002254
Tao Bao9dd909e2017-11-14 11:27:32 -08002255 For non-AVB images, raise exception if the data is too big. Print a warning
2256 if the data is nearing the maximum size.
2257
2258 For AVB images, the actual image size should be identical to the limit.
2259
2260 Args:
2261 data: A string that contains all the data for the partition.
2262 target: The partition name. The ".img" suffix is optional.
2263 info_dict: The dict to be looked up for relevant info.
2264 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002265 if target.endswith(".img"):
2266 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002267 mount_point = "/" + target
2268
Ying Wangf8824af2014-06-03 14:07:27 -07002269 fs_type = None
2270 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002271 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002272 if mount_point == "/userdata":
2273 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002274 p = info_dict["fstab"][mount_point]
2275 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002276 device = p.device
2277 if "/" in device:
2278 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002279 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002280 if not fs_type or not limit:
2281 return
Doug Zongkereef39442009-04-02 12:14:19 -07002282
Andrew Boie0f9aec82012-02-14 09:32:52 -08002283 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002284 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2285 # path.
2286 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2287 if size != limit:
2288 raise ExternalError(
2289 "Mismatching image size for %s: expected %d actual %d" % (
2290 target, limit, size))
2291 else:
2292 pct = float(size) * 100.0 / limit
2293 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2294 if pct >= 99.0:
2295 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002296
2297 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002298 logger.warning("\n WARNING: %s\n", msg)
2299 else:
2300 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002301
2302
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002303def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002304 """Parses the APK certs info from a given target-files zip.
2305
2306 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2307 tuple with the following elements: (1) a dictionary that maps packages to
2308 certs (based on the "certificate" and "private_key" attributes in the file;
2309 (2) a string representing the extension of compressed APKs in the target files
2310 (e.g ".gz", ".bro").
2311
2312 Args:
2313 tf_zip: The input target_files ZipFile (already open).
2314
2315 Returns:
2316 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2317 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2318 no compressed APKs.
2319 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002320 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002321 compressed_extension = None
2322
Tao Bao0f990332017-09-08 19:02:54 -07002323 # META/apkcerts.txt contains the info for _all_ the packages known at build
2324 # time. Filter out the ones that are not installed.
2325 installed_files = set()
2326 for name in tf_zip.namelist():
2327 basename = os.path.basename(name)
2328 if basename:
2329 installed_files.add(basename)
2330
Tao Baoda30cfa2017-12-01 16:19:46 -08002331 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002332 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002333 if not line:
2334 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002335 m = re.match(
2336 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002337 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2338 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002339 line)
2340 if not m:
2341 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002342
Tao Bao818ddf52018-01-05 11:17:34 -08002343 matches = m.groupdict()
2344 cert = matches["CERT"]
2345 privkey = matches["PRIVKEY"]
2346 name = matches["NAME"]
2347 this_compressed_extension = matches["COMPRESSED"]
2348
2349 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2350 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2351 if cert in SPECIAL_CERT_STRINGS and not privkey:
2352 certmap[name] = cert
2353 elif (cert.endswith(OPTIONS.public_key_suffix) and
2354 privkey.endswith(OPTIONS.private_key_suffix) and
2355 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2356 certmap[name] = cert[:-public_key_suffix_len]
2357 else:
2358 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2359
2360 if not this_compressed_extension:
2361 continue
2362
2363 # Only count the installed files.
2364 filename = name + '.' + this_compressed_extension
2365 if filename not in installed_files:
2366 continue
2367
2368 # Make sure that all the values in the compression map have the same
2369 # extension. We don't support multiple compression methods in the same
2370 # system image.
2371 if compressed_extension:
2372 if this_compressed_extension != compressed_extension:
2373 raise ValueError(
2374 "Multiple compressed extensions: {} vs {}".format(
2375 compressed_extension, this_compressed_extension))
2376 else:
2377 compressed_extension = this_compressed_extension
2378
2379 return (certmap,
2380 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002381
2382
Doug Zongkereef39442009-04-02 12:14:19 -07002383COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002384Global options
2385
2386 -p (--path) <dir>
2387 Prepend <dir>/bin to the list of places to search for binaries run by this
2388 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002389
Doug Zongker05d3dea2009-06-22 11:32:31 -07002390 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002391 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002392
Tao Bao30df8b42018-04-23 15:32:53 -07002393 -x (--extra) <key=value>
2394 Add a key/value pair to the 'extras' dict, which device-specific extension
2395 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002396
Doug Zongkereef39442009-04-02 12:14:19 -07002397 -v (--verbose)
2398 Show command lines being executed.
2399
2400 -h (--help)
2401 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002402
2403 --logfile <file>
2404 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002405"""
2406
Kelvin Zhang0876c412020-06-23 15:06:58 -04002407
Doug Zongkereef39442009-04-02 12:14:19 -07002408def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002409 print(docstring.rstrip("\n"))
2410 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002411
2412
2413def ParseOptions(argv,
2414 docstring,
2415 extra_opts="", extra_long_opts=(),
2416 extra_option_handler=None):
2417 """Parse the options in argv and return any arguments that aren't
2418 flags. docstring is the calling module's docstring, to be displayed
2419 for errors and -h. extra_opts and extra_long_opts are for flags
2420 defined by the caller, which are processed by passing them to
2421 extra_option_handler."""
2422
2423 try:
2424 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002425 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002426 ["help", "verbose", "path=", "signapk_path=",
2427 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002428 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002429 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2430 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Jan Monsche147d482021-06-23 12:30:35 +02002431 "extra=", "logfile="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002432 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002433 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002434 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002435 sys.exit(2)
2436
Doug Zongkereef39442009-04-02 12:14:19 -07002437 for o, a in opts:
2438 if o in ("-h", "--help"):
2439 Usage(docstring)
2440 sys.exit()
2441 elif o in ("-v", "--verbose"):
2442 OPTIONS.verbose = True
2443 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002444 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002445 elif o in ("--signapk_path",):
2446 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002447 elif o in ("--signapk_shared_library_path",):
2448 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002449 elif o in ("--extra_signapk_args",):
2450 OPTIONS.extra_signapk_args = shlex.split(a)
2451 elif o in ("--java_path",):
2452 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002453 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002454 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002455 elif o in ("--android_jar_path",):
2456 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002457 elif o in ("--public_key_suffix",):
2458 OPTIONS.public_key_suffix = a
2459 elif o in ("--private_key_suffix",):
2460 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002461 elif o in ("--boot_signer_path",):
2462 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002463 elif o in ("--boot_signer_args",):
2464 OPTIONS.boot_signer_args = shlex.split(a)
2465 elif o in ("--verity_signer_path",):
2466 OPTIONS.verity_signer_path = a
2467 elif o in ("--verity_signer_args",):
2468 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07002469 elif o in ("-s", "--device_specific"):
2470 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002471 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002472 key, value = a.split("=", 1)
2473 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002474 elif o in ("--logfile",):
2475 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002476 else:
2477 if extra_option_handler is None or not extra_option_handler(o, a):
2478 assert False, "unknown option \"%s\"" % (o,)
2479
Doug Zongker85448772014-09-09 14:59:20 -07002480 if OPTIONS.search_path:
2481 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2482 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002483
2484 return args
2485
2486
Tao Bao4c851b12016-09-19 13:54:38 -07002487def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002488 """Make a temp file and add it to the list of things to be deleted
2489 when Cleanup() is called. Return the filename."""
2490 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2491 os.close(fd)
2492 OPTIONS.tempfiles.append(fn)
2493 return fn
2494
2495
Tao Bao1c830bf2017-12-25 10:43:47 -08002496def MakeTempDir(prefix='tmp', suffix=''):
2497 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2498
2499 Returns:
2500 The absolute pathname of the new directory.
2501 """
2502 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2503 OPTIONS.tempfiles.append(dir_name)
2504 return dir_name
2505
2506
Doug Zongkereef39442009-04-02 12:14:19 -07002507def Cleanup():
2508 for i in OPTIONS.tempfiles:
2509 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002510 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002511 else:
2512 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002513 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002514
2515
2516class PasswordManager(object):
2517 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002518 self.editor = os.getenv("EDITOR")
2519 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002520
2521 def GetPasswords(self, items):
2522 """Get passwords corresponding to each string in 'items',
2523 returning a dict. (The dict may have keys in addition to the
2524 values in 'items'.)
2525
2526 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2527 user edit that file to add more needed passwords. If no editor is
2528 available, or $ANDROID_PW_FILE isn't define, prompts the user
2529 interactively in the ordinary way.
2530 """
2531
2532 current = self.ReadFile()
2533
2534 first = True
2535 while True:
2536 missing = []
2537 for i in items:
2538 if i not in current or not current[i]:
2539 missing.append(i)
2540 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002541 if not missing:
2542 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002543
2544 for i in missing:
2545 current[i] = ""
2546
2547 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002548 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002549 if sys.version_info[0] >= 3:
2550 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002551 answer = raw_input("try to edit again? [y]> ").strip()
2552 if answer and answer[0] not in 'yY':
2553 raise RuntimeError("key passwords unavailable")
2554 first = False
2555
2556 current = self.UpdateAndReadFile(current)
2557
Kelvin Zhang0876c412020-06-23 15:06:58 -04002558 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002559 """Prompt the user to enter a value (password) for each key in
2560 'current' whose value is fales. Returns a new dict with all the
2561 values.
2562 """
2563 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002564 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002565 if v:
2566 result[k] = v
2567 else:
2568 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002569 result[k] = getpass.getpass(
2570 "Enter password for %s key> " % k).strip()
2571 if result[k]:
2572 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002573 return result
2574
2575 def UpdateAndReadFile(self, current):
2576 if not self.editor or not self.pwfile:
2577 return self.PromptResult(current)
2578
2579 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002580 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002581 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2582 f.write("# (Additional spaces are harmless.)\n\n")
2583
2584 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002585 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002586 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002587 f.write("[[[ %s ]]] %s\n" % (v, k))
2588 if not v and first_line is None:
2589 # position cursor on first line with no password.
2590 first_line = i + 4
2591 f.close()
2592
Tao Bao986ee862018-10-04 15:46:16 -07002593 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002594
2595 return self.ReadFile()
2596
2597 def ReadFile(self):
2598 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002599 if self.pwfile is None:
2600 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002601 try:
2602 f = open(self.pwfile, "r")
2603 for line in f:
2604 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002605 if not line or line[0] == '#':
2606 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002607 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2608 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002609 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002610 else:
2611 result[m.group(2)] = m.group(1)
2612 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002613 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002614 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002615 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002616 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002617
2618
Dan Albert8e0178d2015-01-27 15:53:15 -08002619def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2620 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002621
2622 # http://b/18015246
2623 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2624 # for files larger than 2GiB. We can work around this by adjusting their
2625 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2626 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2627 # it isn't clear to me exactly what circumstances cause this).
2628 # `zipfile.write()` must be used directly to work around this.
2629 #
2630 # This mess can be avoided if we port to python3.
2631 saved_zip64_limit = zipfile.ZIP64_LIMIT
2632 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2633
2634 if compress_type is None:
2635 compress_type = zip_file.compression
2636 if arcname is None:
2637 arcname = filename
2638
2639 saved_stat = os.stat(filename)
2640
2641 try:
2642 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2643 # file to be zipped and reset it when we're done.
2644 os.chmod(filename, perms)
2645
2646 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002647 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2648 # intentional. zip stores datetimes in local time without a time zone
2649 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2650 # in the zip archive.
2651 local_epoch = datetime.datetime.fromtimestamp(0)
2652 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002653 os.utime(filename, (timestamp, timestamp))
2654
2655 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2656 finally:
2657 os.chmod(filename, saved_stat.st_mode)
2658 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2659 zipfile.ZIP64_LIMIT = saved_zip64_limit
2660
2661
Tao Bao58c1b962015-05-20 09:32:18 -07002662def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002663 compress_type=None):
2664 """Wrap zipfile.writestr() function to work around the zip64 limit.
2665
2666 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2667 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2668 when calling crc32(bytes).
2669
2670 But it still works fine to write a shorter string into a large zip file.
2671 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2672 when we know the string won't be too long.
2673 """
2674
2675 saved_zip64_limit = zipfile.ZIP64_LIMIT
2676 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2677
2678 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2679 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002680 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002681 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002682 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002683 else:
Tao Baof3282b42015-04-01 11:21:55 -07002684 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002685 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2686 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2687 # such a case (since
2688 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2689 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2690 # permission bits. We follow the logic in Python 3 to get consistent
2691 # behavior between using the two versions.
2692 if not zinfo.external_attr:
2693 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002694
2695 # If compress_type is given, it overrides the value in zinfo.
2696 if compress_type is not None:
2697 zinfo.compress_type = compress_type
2698
Tao Bao58c1b962015-05-20 09:32:18 -07002699 # If perms is given, it has a priority.
2700 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002701 # If perms doesn't set the file type, mark it as a regular file.
2702 if perms & 0o770000 == 0:
2703 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002704 zinfo.external_attr = perms << 16
2705
Tao Baof3282b42015-04-01 11:21:55 -07002706 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002707 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2708
Dan Albert8b72aef2015-03-23 19:13:21 -07002709 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002710 zipfile.ZIP64_LIMIT = saved_zip64_limit
2711
2712
Tao Bao89d7ab22017-12-14 17:05:33 -08002713def ZipDelete(zip_filename, entries):
2714 """Deletes entries from a ZIP file.
2715
2716 Since deleting entries from a ZIP file is not supported, it shells out to
2717 'zip -d'.
2718
2719 Args:
2720 zip_filename: The name of the ZIP file.
2721 entries: The name of the entry, or the list of names to be deleted.
2722
2723 Raises:
2724 AssertionError: In case of non-zero return from 'zip'.
2725 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002726 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002727 entries = [entries]
2728 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002729 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002730
2731
Tao Baof3282b42015-04-01 11:21:55 -07002732def ZipClose(zip_file):
2733 # http://b/18015246
2734 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2735 # central directory.
2736 saved_zip64_limit = zipfile.ZIP64_LIMIT
2737 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2738
2739 zip_file.close()
2740
2741 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002742
2743
2744class DeviceSpecificParams(object):
2745 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002746
Doug Zongker05d3dea2009-06-22 11:32:31 -07002747 def __init__(self, **kwargs):
2748 """Keyword arguments to the constructor become attributes of this
2749 object, which is passed to all functions in the device-specific
2750 module."""
Tao Bao38884282019-07-10 22:20:56 -07002751 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002752 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002753 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002754
2755 if self.module is None:
2756 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002757 if not path:
2758 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002759 try:
2760 if os.path.isdir(path):
2761 info = imp.find_module("releasetools", [path])
2762 else:
2763 d, f = os.path.split(path)
2764 b, x = os.path.splitext(f)
2765 if x == ".py":
2766 f = b
2767 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002768 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002769 self.module = imp.load_module("device_specific", *info)
2770 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002771 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002772
2773 def _DoCall(self, function_name, *args, **kwargs):
2774 """Call the named function in the device-specific module, passing
2775 the given args and kwargs. The first argument to the call will be
2776 the DeviceSpecific object itself. If there is no module, or the
2777 module does not define the function, return the value of the
2778 'default' kwarg (which itself defaults to None)."""
2779 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002780 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002781 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2782
2783 def FullOTA_Assertions(self):
2784 """Called after emitting the block of assertions at the top of a
2785 full OTA package. Implementations can add whatever additional
2786 assertions they like."""
2787 return self._DoCall("FullOTA_Assertions")
2788
Doug Zongkere5ff5902012-01-17 10:55:37 -08002789 def FullOTA_InstallBegin(self):
2790 """Called at the start of full OTA installation."""
2791 return self._DoCall("FullOTA_InstallBegin")
2792
Yifan Hong10c530d2018-12-27 17:34:18 -08002793 def FullOTA_GetBlockDifferences(self):
2794 """Called during full OTA installation and verification.
2795 Implementation should return a list of BlockDifference objects describing
2796 the update on each additional partitions.
2797 """
2798 return self._DoCall("FullOTA_GetBlockDifferences")
2799
Doug Zongker05d3dea2009-06-22 11:32:31 -07002800 def FullOTA_InstallEnd(self):
2801 """Called at the end of full OTA installation; typically this is
2802 used to install the image for the device's baseband processor."""
2803 return self._DoCall("FullOTA_InstallEnd")
2804
2805 def IncrementalOTA_Assertions(self):
2806 """Called after emitting the block of assertions at the top of an
2807 incremental OTA package. Implementations can add whatever
2808 additional assertions they like."""
2809 return self._DoCall("IncrementalOTA_Assertions")
2810
Doug Zongkere5ff5902012-01-17 10:55:37 -08002811 def IncrementalOTA_VerifyBegin(self):
2812 """Called at the start of the verification phase of incremental
2813 OTA installation; additional checks can be placed here to abort
2814 the script before any changes are made."""
2815 return self._DoCall("IncrementalOTA_VerifyBegin")
2816
Doug Zongker05d3dea2009-06-22 11:32:31 -07002817 def IncrementalOTA_VerifyEnd(self):
2818 """Called at the end of the verification phase of incremental OTA
2819 installation; additional checks can be placed here to abort the
2820 script before any changes are made."""
2821 return self._DoCall("IncrementalOTA_VerifyEnd")
2822
Doug Zongkere5ff5902012-01-17 10:55:37 -08002823 def IncrementalOTA_InstallBegin(self):
2824 """Called at the start of incremental OTA installation (after
2825 verification is complete)."""
2826 return self._DoCall("IncrementalOTA_InstallBegin")
2827
Yifan Hong10c530d2018-12-27 17:34:18 -08002828 def IncrementalOTA_GetBlockDifferences(self):
2829 """Called during incremental OTA installation and verification.
2830 Implementation should return a list of BlockDifference objects describing
2831 the update on each additional partitions.
2832 """
2833 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2834
Doug Zongker05d3dea2009-06-22 11:32:31 -07002835 def IncrementalOTA_InstallEnd(self):
2836 """Called at the end of incremental OTA installation; typically
2837 this is used to install the image for the device's baseband
2838 processor."""
2839 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002840
Tao Bao9bc6bb22015-11-09 16:58:28 -08002841 def VerifyOTA_Assertions(self):
2842 return self._DoCall("VerifyOTA_Assertions")
2843
Tao Bao76def242017-11-21 09:25:31 -08002844
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002845class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002846 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002847 self.name = name
2848 self.data = data
2849 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002850 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002851 self.sha1 = sha1(data).hexdigest()
2852
2853 @classmethod
2854 def FromLocalFile(cls, name, diskname):
2855 f = open(diskname, "rb")
2856 data = f.read()
2857 f.close()
2858 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002859
2860 def WriteToTemp(self):
2861 t = tempfile.NamedTemporaryFile()
2862 t.write(self.data)
2863 t.flush()
2864 return t
2865
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002866 def WriteToDir(self, d):
2867 with open(os.path.join(d, self.name), "wb") as fp:
2868 fp.write(self.data)
2869
Geremy Condra36bd3652014-02-06 19:45:10 -08002870 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002871 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002872
Tao Bao76def242017-11-21 09:25:31 -08002873
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002874DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002875 ".gz": "imgdiff",
2876 ".zip": ["imgdiff", "-z"],
2877 ".jar": ["imgdiff", "-z"],
2878 ".apk": ["imgdiff", "-z"],
2879 ".img": "imgdiff",
2880}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002881
Tao Bao76def242017-11-21 09:25:31 -08002882
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002883class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002884 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002885 self.tf = tf
2886 self.sf = sf
2887 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002888 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002889
2890 def ComputePatch(self):
2891 """Compute the patch (as a string of data) needed to turn sf into
2892 tf. Returns the same tuple as GetPatch()."""
2893
2894 tf = self.tf
2895 sf = self.sf
2896
Doug Zongker24cd2802012-08-14 16:36:15 -07002897 if self.diff_program:
2898 diff_program = self.diff_program
2899 else:
2900 ext = os.path.splitext(tf.name)[1]
2901 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002902
2903 ttemp = tf.WriteToTemp()
2904 stemp = sf.WriteToTemp()
2905
2906 ext = os.path.splitext(tf.name)[1]
2907
2908 try:
2909 ptemp = tempfile.NamedTemporaryFile()
2910 if isinstance(diff_program, list):
2911 cmd = copy.copy(diff_program)
2912 else:
2913 cmd = [diff_program]
2914 cmd.append(stemp.name)
2915 cmd.append(ttemp.name)
2916 cmd.append(ptemp.name)
2917 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002918 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002919
Doug Zongkerf8340082014-08-05 10:39:37 -07002920 def run():
2921 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002922 if e:
2923 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002924 th = threading.Thread(target=run)
2925 th.start()
2926 th.join(timeout=300) # 5 mins
2927 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002928 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002929 p.terminate()
2930 th.join(5)
2931 if th.is_alive():
2932 p.kill()
2933 th.join()
2934
Tianjie Xua2a9f992018-01-05 15:15:54 -08002935 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002936 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002937 self.patch = None
2938 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002939 diff = ptemp.read()
2940 finally:
2941 ptemp.close()
2942 stemp.close()
2943 ttemp.close()
2944
2945 self.patch = diff
2946 return self.tf, self.sf, self.patch
2947
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002948 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002949 """Returns a tuple of (target_file, source_file, patch_data).
2950
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002951 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002952 computing the patch failed.
2953 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002954 return self.tf, self.sf, self.patch
2955
2956
2957def ComputeDifferences(diffs):
2958 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002959 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002960
2961 # Do the largest files first, to try and reduce the long-pole effect.
2962 by_size = [(i.tf.size, i) for i in diffs]
2963 by_size.sort(reverse=True)
2964 by_size = [i[1] for i in by_size]
2965
2966 lock = threading.Lock()
2967 diff_iter = iter(by_size) # accessed under lock
2968
2969 def worker():
2970 try:
2971 lock.acquire()
2972 for d in diff_iter:
2973 lock.release()
2974 start = time.time()
2975 d.ComputePatch()
2976 dur = time.time() - start
2977 lock.acquire()
2978
2979 tf, sf, patch = d.GetPatch()
2980 if sf.name == tf.name:
2981 name = tf.name
2982 else:
2983 name = "%s (%s)" % (tf.name, sf.name)
2984 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002985 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002986 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002987 logger.info(
2988 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2989 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002990 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002991 except Exception:
2992 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002993 raise
2994
2995 # start worker threads; wait for them all to finish.
2996 threads = [threading.Thread(target=worker)
2997 for i in range(OPTIONS.worker_threads)]
2998 for th in threads:
2999 th.start()
3000 while threads:
3001 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003002
3003
Dan Albert8b72aef2015-03-23 19:13:21 -07003004class BlockDifference(object):
3005 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003006 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003007 self.tgt = tgt
3008 self.src = src
3009 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003010 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003011 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003012
Tao Baodd2a5892015-03-12 12:32:37 -07003013 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003014 version = max(
3015 int(i) for i in
3016 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003017 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003018 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003019
Tianjie Xu41976c72019-07-03 13:57:01 -07003020 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3021 version=self.version,
3022 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003023 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003024 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003025 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003026 self.touched_src_ranges = b.touched_src_ranges
3027 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003028
Yifan Hong10c530d2018-12-27 17:34:18 -08003029 # On devices with dynamic partitions, for new partitions,
3030 # src is None but OPTIONS.source_info_dict is not.
3031 if OPTIONS.source_info_dict is None:
3032 is_dynamic_build = OPTIONS.info_dict.get(
3033 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003034 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003035 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003036 is_dynamic_build = OPTIONS.source_info_dict.get(
3037 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003038 is_dynamic_source = partition in shlex.split(
3039 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003040
Yifan Hongbb2658d2019-01-25 12:30:58 -08003041 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003042 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3043
Yifan Hongbb2658d2019-01-25 12:30:58 -08003044 # For dynamic partitions builds, check partition list in both source
3045 # and target build because new partitions may be added, and existing
3046 # partitions may be removed.
3047 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3048
Yifan Hong10c530d2018-12-27 17:34:18 -08003049 if is_dynamic:
3050 self.device = 'map_partition("%s")' % partition
3051 else:
3052 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003053 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3054 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003055 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003056 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3057 OPTIONS.source_info_dict)
3058 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003059
Tao Baod8d14be2016-02-04 14:26:02 -08003060 @property
3061 def required_cache(self):
3062 return self._required_cache
3063
Tao Bao76def242017-11-21 09:25:31 -08003064 def WriteScript(self, script, output_zip, progress=None,
3065 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003066 if not self.src:
3067 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003068 script.Print("Patching %s image unconditionally..." % (self.partition,))
3069 else:
3070 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003071
Dan Albert8b72aef2015-03-23 19:13:21 -07003072 if progress:
3073 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003074 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003075
3076 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003077 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003078
Tao Bao9bc6bb22015-11-09 16:58:28 -08003079 def WriteStrictVerifyScript(self, script):
3080 """Verify all the blocks in the care_map, including clobbered blocks.
3081
3082 This differs from the WriteVerifyScript() function: a) it prints different
3083 error messages; b) it doesn't allow half-way updated images to pass the
3084 verification."""
3085
3086 partition = self.partition
3087 script.Print("Verifying %s..." % (partition,))
3088 ranges = self.tgt.care_map
3089 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003090 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003091 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3092 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003093 self.device, ranges_str,
3094 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003095 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003096 script.AppendExtra("")
3097
Tao Baod522bdc2016-04-12 15:53:16 -07003098 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003099 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003100
3101 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003102 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003103 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003104
3105 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003106 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003107 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003108 ranges = self.touched_src_ranges
3109 expected_sha1 = self.touched_src_sha1
3110 else:
3111 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3112 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003113
3114 # No blocks to be checked, skipping.
3115 if not ranges:
3116 return
3117
Tao Bao5ece99d2015-05-12 11:42:31 -07003118 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003119 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003120 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003121 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3122 '"%s.patch.dat")) then' % (
3123 self.device, ranges_str, expected_sha1,
3124 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003125 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003126 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003127
Tianjie Xufc3422a2015-12-15 11:53:59 -08003128 if self.version >= 4:
3129
3130 # Bug: 21124327
3131 # When generating incrementals for the system and vendor partitions in
3132 # version 4 or newer, explicitly check the first block (which contains
3133 # the superblock) of the partition to see if it's what we expect. If
3134 # this check fails, give an explicit log message about the partition
3135 # having been remounted R/W (the most likely explanation).
3136 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003137 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003138
3139 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003140 if partition == "system":
3141 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3142 else:
3143 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003144 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003145 'ifelse (block_image_recover({device}, "{ranges}") && '
3146 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003147 'package_extract_file("{partition}.transfer.list"), '
3148 '"{partition}.new.dat", "{partition}.patch.dat"), '
3149 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003150 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003151 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003152 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003153
Tao Baodd2a5892015-03-12 12:32:37 -07003154 # Abort the OTA update. Note that the incremental OTA cannot be applied
3155 # even if it may match the checksum of the target partition.
3156 # a) If version < 3, operations like move and erase will make changes
3157 # unconditionally and damage the partition.
3158 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003159 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003160 if partition == "system":
3161 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3162 else:
3163 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3164 script.AppendExtra((
3165 'abort("E%d: %s partition has unexpected contents");\n'
3166 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003167
Yifan Hong10c530d2018-12-27 17:34:18 -08003168 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003169 partition = self.partition
3170 script.Print('Verifying the updated %s image...' % (partition,))
3171 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3172 ranges = self.tgt.care_map
3173 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003174 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003175 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003176 self.device, ranges_str,
3177 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003178
3179 # Bug: 20881595
3180 # Verify that extended blocks are really zeroed out.
3181 if self.tgt.extended:
3182 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003183 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003184 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003185 self.device, ranges_str,
3186 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003187 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003188 if partition == "system":
3189 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3190 else:
3191 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003192 script.AppendExtra(
3193 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003194 ' abort("E%d: %s partition has unexpected non-zero contents after '
3195 'OTA update");\n'
3196 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003197 else:
3198 script.Print('Verified the updated %s image.' % (partition,))
3199
Tianjie Xu209db462016-05-24 17:34:52 -07003200 if partition == "system":
3201 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3202 else:
3203 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3204
Tao Bao5fcaaef2015-06-01 13:40:49 -07003205 script.AppendExtra(
3206 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003207 ' abort("E%d: %s partition has unexpected contents after OTA '
3208 'update");\n'
3209 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003210
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003211 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003212 ZipWrite(output_zip,
3213 '{}.transfer.list'.format(self.path),
3214 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003215
Tao Bao76def242017-11-21 09:25:31 -08003216 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3217 # its size. Quailty 9 almost triples the compression time but doesn't
3218 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003219 # zip | brotli(quality 6) | brotli(quality 9)
3220 # compressed_size: 942M | 869M (~8% reduced) | 854M
3221 # compression_time: 75s | 265s | 719s
3222 # decompression_time: 15s | 25s | 25s
3223
3224 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003225 brotli_cmd = ['brotli', '--quality=6',
3226 '--output={}.new.dat.br'.format(self.path),
3227 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003228 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003229 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003230
3231 new_data_name = '{}.new.dat.br'.format(self.partition)
3232 ZipWrite(output_zip,
3233 '{}.new.dat.br'.format(self.path),
3234 new_data_name,
3235 compress_type=zipfile.ZIP_STORED)
3236 else:
3237 new_data_name = '{}.new.dat'.format(self.partition)
3238 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3239
Dan Albert8e0178d2015-01-27 15:53:15 -08003240 ZipWrite(output_zip,
3241 '{}.patch.dat'.format(self.path),
3242 '{}.patch.dat'.format(self.partition),
3243 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003244
Tianjie Xu209db462016-05-24 17:34:52 -07003245 if self.partition == "system":
3246 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3247 else:
3248 code = ErrorCode.VENDOR_UPDATE_FAILURE
3249
Yifan Hong10c530d2018-12-27 17:34:18 -08003250 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003251 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003252 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003253 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003254 device=self.device, partition=self.partition,
3255 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003256 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003257
Kelvin Zhang0876c412020-06-23 15:06:58 -04003258 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003259 data = source.ReadRangeSet(ranges)
3260 ctx = sha1()
3261
3262 for p in data:
3263 ctx.update(p)
3264
3265 return ctx.hexdigest()
3266
Kelvin Zhang0876c412020-06-23 15:06:58 -04003267 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003268 """Return the hash value for all zero blocks."""
3269 zero_block = '\x00' * 4096
3270 ctx = sha1()
3271 for _ in range(num_blocks):
3272 ctx.update(zero_block)
3273
3274 return ctx.hexdigest()
3275
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003276
Tianjie Xu41976c72019-07-03 13:57:01 -07003277# Expose these two classes to support vendor-specific scripts
3278DataImage = images.DataImage
3279EmptyImage = images.EmptyImage
3280
Tao Bao76def242017-11-21 09:25:31 -08003281
Doug Zongker96a57e72010-09-26 14:57:41 -07003282# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003283PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003284 "ext4": "EMMC",
3285 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003286 "f2fs": "EMMC",
3287 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003288}
Doug Zongker96a57e72010-09-26 14:57:41 -07003289
Kelvin Zhang0876c412020-06-23 15:06:58 -04003290
Yifan Hongbdb32012020-05-07 12:38:53 -07003291def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3292 """
3293 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3294 backwards compatibility. It aborts if the fstab entry has slotselect option
3295 (unless check_no_slot is explicitly set to False).
3296 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003297 fstab = info["fstab"]
3298 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003299 if check_no_slot:
3300 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003301 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003302 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3303 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003304 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003305
3306
Yifan Hongbdb32012020-05-07 12:38:53 -07003307def GetTypeAndDeviceExpr(mount_point, info):
3308 """
3309 Return the filesystem of the partition, and an edify expression that evaluates
3310 to the device at runtime.
3311 """
3312 fstab = info["fstab"]
3313 if fstab:
3314 p = fstab[mount_point]
3315 device_expr = '"%s"' % fstab[mount_point].device
3316 if p.slotselect:
3317 device_expr = 'add_slot_suffix(%s)' % device_expr
3318 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003319 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003320
3321
3322def GetEntryForDevice(fstab, device):
3323 """
3324 Returns:
3325 The first entry in fstab whose device is the given value.
3326 """
3327 if not fstab:
3328 return None
3329 for mount_point in fstab:
3330 if fstab[mount_point].device == device:
3331 return fstab[mount_point]
3332 return None
3333
Kelvin Zhang0876c412020-06-23 15:06:58 -04003334
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003335def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003336 """Parses and converts a PEM-encoded certificate into DER-encoded.
3337
3338 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3339
3340 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003341 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003342 """
3343 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003344 save = False
3345 for line in data.split("\n"):
3346 if "--END CERTIFICATE--" in line:
3347 break
3348 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003349 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003350 if "--BEGIN CERTIFICATE--" in line:
3351 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003352 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003353 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003354
Tao Bao04e1f012018-02-04 12:13:35 -08003355
3356def ExtractPublicKey(cert):
3357 """Extracts the public key (PEM-encoded) from the given certificate file.
3358
3359 Args:
3360 cert: The certificate filename.
3361
3362 Returns:
3363 The public key string.
3364
3365 Raises:
3366 AssertionError: On non-zero return from 'openssl'.
3367 """
3368 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3369 # While openssl 1.1 writes the key into the given filename followed by '-out',
3370 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3371 # stdout instead.
3372 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3373 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3374 pubkey, stderrdata = proc.communicate()
3375 assert proc.returncode == 0, \
3376 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3377 return pubkey
3378
3379
Tao Bao1ac886e2019-06-26 11:58:22 -07003380def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003381 """Extracts the AVB public key from the given public or private key.
3382
3383 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003384 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003385 key: The input key file, which should be PEM-encoded public or private key.
3386
3387 Returns:
3388 The path to the extracted AVB public key file.
3389 """
3390 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3391 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003392 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003393 return output
3394
3395
Doug Zongker412c02f2014-02-13 10:58:24 -08003396def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3397 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003398 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003399
Tao Bao6d5d6232018-03-09 17:04:42 -08003400 Most of the space in the boot and recovery images is just the kernel, which is
3401 identical for the two, so the resulting patch should be efficient. Add it to
3402 the output zip, along with a shell script that is run from init.rc on first
3403 boot to actually do the patching and install the new recovery image.
3404
3405 Args:
3406 input_dir: The top-level input directory of the target-files.zip.
3407 output_sink: The callback function that writes the result.
3408 recovery_img: File object for the recovery image.
3409 boot_img: File objects for the boot image.
3410 info_dict: A dict returned by common.LoadInfoDict() on the input
3411 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003412 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003413 if info_dict is None:
3414 info_dict = OPTIONS.info_dict
3415
Tao Bao6d5d6232018-03-09 17:04:42 -08003416 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003417 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3418
3419 if board_uses_vendorimage:
3420 # In this case, the output sink is rooted at VENDOR
3421 recovery_img_path = "etc/recovery.img"
3422 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3423 sh_dir = "bin"
3424 else:
3425 # In this case the output sink is rooted at SYSTEM
3426 recovery_img_path = "vendor/etc/recovery.img"
3427 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3428 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003429
Tao Baof2cffbd2015-07-22 12:33:18 -07003430 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003431 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003432
3433 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003434 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003435 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003436 # With system-root-image, boot and recovery images will have mismatching
3437 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3438 # to handle such a case.
3439 if system_root_image:
3440 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003441 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003442 assert not os.path.exists(path)
3443 else:
3444 diff_program = ["imgdiff"]
3445 if os.path.exists(path):
3446 diff_program.append("-b")
3447 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003448 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003449 else:
3450 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003451
3452 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3453 _, _, patch = d.ComputePatch()
3454 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003455
Dan Albertebb19aa2015-03-27 19:11:53 -07003456 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003457 # The following GetTypeAndDevice()s need to use the path in the target
3458 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003459 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3460 check_no_slot=False)
3461 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3462 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003463 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003464 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003465
Tao Baof2cffbd2015-07-22 12:33:18 -07003466 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003467
3468 # Note that we use /vendor to refer to the recovery resources. This will
3469 # work for a separate vendor partition mounted at /vendor or a
3470 # /system/vendor subdirectory on the system partition, for which init will
3471 # create a symlink from /vendor to /system/vendor.
3472
3473 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003474if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3475 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003476 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003477 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3478 log -t recovery "Installing new recovery image: succeeded" || \\
3479 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003480else
3481 log -t recovery "Recovery image already installed"
3482fi
3483""" % {'type': recovery_type,
3484 'device': recovery_device,
3485 'sha1': recovery_img.sha1,
3486 'size': recovery_img.size}
3487 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003488 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003489if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3490 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003491 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003492 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3493 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3494 log -t recovery "Installing new recovery image: succeeded" || \\
3495 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003496else
3497 log -t recovery "Recovery image already installed"
3498fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003499""" % {'boot_size': boot_img.size,
3500 'boot_sha1': boot_img.sha1,
3501 'recovery_size': recovery_img.size,
3502 'recovery_sha1': recovery_img.sha1,
3503 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003504 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003505 'recovery_type': recovery_type,
3506 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003507 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003508
Bill Peckhame868aec2019-09-17 17:06:47 -07003509 # The install script location moved from /system/etc to /system/bin in the L
3510 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3511 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003512
Tao Bao32fcdab2018-10-12 10:30:39 -07003513 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003514
Tao Baoda30cfa2017-12-01 16:19:46 -08003515 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003516
3517
3518class DynamicPartitionUpdate(object):
3519 def __init__(self, src_group=None, tgt_group=None, progress=None,
3520 block_difference=None):
3521 self.src_group = src_group
3522 self.tgt_group = tgt_group
3523 self.progress = progress
3524 self.block_difference = block_difference
3525
3526 @property
3527 def src_size(self):
3528 if not self.block_difference:
3529 return 0
3530 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3531
3532 @property
3533 def tgt_size(self):
3534 if not self.block_difference:
3535 return 0
3536 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3537
3538 @staticmethod
3539 def _GetSparseImageSize(img):
3540 if not img:
3541 return 0
3542 return img.blocksize * img.total_blocks
3543
3544
3545class DynamicGroupUpdate(object):
3546 def __init__(self, src_size=None, tgt_size=None):
3547 # None: group does not exist. 0: no size limits.
3548 self.src_size = src_size
3549 self.tgt_size = tgt_size
3550
3551
3552class DynamicPartitionsDifference(object):
3553 def __init__(self, info_dict, block_diffs, progress_dict=None,
3554 source_info_dict=None):
3555 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003556 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003557
3558 self._remove_all_before_apply = False
3559 if source_info_dict is None:
3560 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003561 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003562
Tao Baof1113e92019-06-18 12:10:14 -07003563 block_diff_dict = collections.OrderedDict(
3564 [(e.partition, e) for e in block_diffs])
3565
Yifan Hong10c530d2018-12-27 17:34:18 -08003566 assert len(block_diff_dict) == len(block_diffs), \
3567 "Duplicated BlockDifference object for {}".format(
3568 [partition for partition, count in
3569 collections.Counter(e.partition for e in block_diffs).items()
3570 if count > 1])
3571
Yifan Hong79997e52019-01-23 16:56:19 -08003572 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003573
3574 for p, block_diff in block_diff_dict.items():
3575 self._partition_updates[p] = DynamicPartitionUpdate()
3576 self._partition_updates[p].block_difference = block_diff
3577
3578 for p, progress in progress_dict.items():
3579 if p in self._partition_updates:
3580 self._partition_updates[p].progress = progress
3581
3582 tgt_groups = shlex.split(info_dict.get(
3583 "super_partition_groups", "").strip())
3584 src_groups = shlex.split(source_info_dict.get(
3585 "super_partition_groups", "").strip())
3586
3587 for g in tgt_groups:
3588 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003589 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003590 assert p in self._partition_updates, \
3591 "{} is in target super_{}_partition_list but no BlockDifference " \
3592 "object is provided.".format(p, g)
3593 self._partition_updates[p].tgt_group = g
3594
3595 for g in src_groups:
3596 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003597 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003598 assert p in self._partition_updates, \
3599 "{} is in source super_{}_partition_list but no BlockDifference " \
3600 "object is provided.".format(p, g)
3601 self._partition_updates[p].src_group = g
3602
Yifan Hong45433e42019-01-18 13:55:25 -08003603 target_dynamic_partitions = set(shlex.split(info_dict.get(
3604 "dynamic_partition_list", "").strip()))
3605 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3606 if u.tgt_size)
3607 assert block_diffs_with_target == target_dynamic_partitions, \
3608 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3609 list(target_dynamic_partitions), list(block_diffs_with_target))
3610
3611 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3612 "dynamic_partition_list", "").strip()))
3613 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3614 if u.src_size)
3615 assert block_diffs_with_source == source_dynamic_partitions, \
3616 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3617 list(source_dynamic_partitions), list(block_diffs_with_source))
3618
Yifan Hong10c530d2018-12-27 17:34:18 -08003619 if self._partition_updates:
3620 logger.info("Updating dynamic partitions %s",
3621 self._partition_updates.keys())
3622
Yifan Hong79997e52019-01-23 16:56:19 -08003623 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003624
3625 for g in tgt_groups:
3626 self._group_updates[g] = DynamicGroupUpdate()
3627 self._group_updates[g].tgt_size = int(info_dict.get(
3628 "super_%s_group_size" % g, "0").strip())
3629
3630 for g in src_groups:
3631 if g not in self._group_updates:
3632 self._group_updates[g] = DynamicGroupUpdate()
3633 self._group_updates[g].src_size = int(source_info_dict.get(
3634 "super_%s_group_size" % g, "0").strip())
3635
3636 self._Compute()
3637
3638 def WriteScript(self, script, output_zip, write_verify_script=False):
3639 script.Comment('--- Start patching dynamic partitions ---')
3640 for p, u in self._partition_updates.items():
3641 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3642 script.Comment('Patch partition %s' % p)
3643 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3644 write_verify_script=False)
3645
3646 op_list_path = MakeTempFile()
3647 with open(op_list_path, 'w') as f:
3648 for line in self._op_list:
3649 f.write('{}\n'.format(line))
3650
3651 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3652
3653 script.Comment('Update dynamic partition metadata')
3654 script.AppendExtra('assert(update_dynamic_partitions('
3655 'package_extract_file("dynamic_partitions_op_list")));')
3656
3657 if write_verify_script:
3658 for p, u in self._partition_updates.items():
3659 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3660 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003661 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003662
3663 for p, u in self._partition_updates.items():
3664 if u.tgt_size and u.src_size <= u.tgt_size:
3665 script.Comment('Patch partition %s' % p)
3666 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3667 write_verify_script=write_verify_script)
3668 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003669 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003670
3671 script.Comment('--- End patching dynamic partitions ---')
3672
3673 def _Compute(self):
3674 self._op_list = list()
3675
3676 def append(line):
3677 self._op_list.append(line)
3678
3679 def comment(line):
3680 self._op_list.append("# %s" % line)
3681
3682 if self._remove_all_before_apply:
3683 comment('Remove all existing dynamic partitions and groups before '
3684 'applying full OTA')
3685 append('remove_all_groups')
3686
3687 for p, u in self._partition_updates.items():
3688 if u.src_group and not u.tgt_group:
3689 append('remove %s' % p)
3690
3691 for p, u in self._partition_updates.items():
3692 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3693 comment('Move partition %s from %s to default' % (p, u.src_group))
3694 append('move %s default' % p)
3695
3696 for p, u in self._partition_updates.items():
3697 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3698 comment('Shrink partition %s from %d to %d' %
3699 (p, u.src_size, u.tgt_size))
3700 append('resize %s %s' % (p, u.tgt_size))
3701
3702 for g, u in self._group_updates.items():
3703 if u.src_size is not None and u.tgt_size is None:
3704 append('remove_group %s' % g)
3705 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003706 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003707 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3708 append('resize_group %s %d' % (g, u.tgt_size))
3709
3710 for g, u in self._group_updates.items():
3711 if u.src_size is None and u.tgt_size is not None:
3712 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3713 append('add_group %s %d' % (g, u.tgt_size))
3714 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003715 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003716 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3717 append('resize_group %s %d' % (g, u.tgt_size))
3718
3719 for p, u in self._partition_updates.items():
3720 if u.tgt_group and not u.src_group:
3721 comment('Add partition %s to group %s' % (p, u.tgt_group))
3722 append('add %s %s' % (p, u.tgt_group))
3723
3724 for p, u in self._partition_updates.items():
3725 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003726 comment('Grow partition %s from %d to %d' %
3727 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003728 append('resize %s %d' % (p, u.tgt_size))
3729
3730 for p, u in self._partition_updates.items():
3731 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3732 comment('Move partition %s from default to %s' %
3733 (p, u.tgt_group))
3734 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003735
3736
jiajia tangf3f842b2021-03-17 21:49:44 +08003737def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003738 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003739 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003740
3741 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003742 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003743
3744 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003745 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003746 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003747 tmp_dir = MakeTempDir('boot_', suffix='.img')
3748 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003749 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3750 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003751 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3752 if not os.path.isfile(ramdisk):
3753 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3754 return None
3755 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003756 if ramdisk_format == RamdiskFormat.LZ4:
3757 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3758 elif ramdisk_format == RamdiskFormat.GZ:
3759 with open(ramdisk, 'rb') as input_stream:
3760 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003761 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3762 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003763 p2.wait()
3764 else:
3765 logger.error('Only support lz4 or minigzip ramdisk format.')
3766 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003767
3768 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3769 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3770 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3771 # the host environment.
3772 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003773 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003774
Yifan Hongc65a0542021-01-07 14:21:01 -08003775 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3776 prop_file = os.path.join(extracted_ramdisk, search_path)
3777 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003778 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003779 logger.warning(
3780 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003781
Yifan Hong7dc51172021-01-12 11:27:39 -08003782 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003783
Yifan Hong85ac5012021-01-07 14:43:46 -08003784 except ExternalError as e:
3785 logger.warning('Unable to get boot image build props: %s', e)
3786 return None
3787
3788
3789def GetBootImageTimestamp(boot_img):
3790 """
3791 Get timestamp from ramdisk within the boot image
3792
3793 Args:
3794 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3795
3796 Return:
3797 An integer that corresponds to the timestamp of the boot image, or None
3798 if file has unknown format. Raise exception if an unexpected error has
3799 occurred.
3800 """
3801 prop_file = GetBootImageBuildProp(boot_img)
3802 if not prop_file:
3803 return None
3804
3805 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3806 if props is None:
3807 return None
3808
3809 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003810 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3811 if timestamp:
3812 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003813 logger.warning(
3814 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003815 return None
3816
3817 except ExternalError as e:
3818 logger.warning('Unable to get boot image timestamp: %s', e)
3819 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003820
3821
3822def GetCareMap(which, imgname):
3823 """Returns the care_map string for the given partition.
3824
3825 Args:
3826 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3827 imgname: The filename of the image.
3828
3829 Returns:
3830 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3831 RangeSet; or None.
3832 """
3833 assert which in PARTITIONS_WITH_CARE_MAP
3834
3835 # which + "_image_size" contains the size that the actual filesystem image
3836 # resides in, which is all that needs to be verified. The additional blocks in
3837 # the image file contain verity metadata, by reading which would trigger
3838 # invalid reads.
3839 image_size = OPTIONS.info_dict.get(which + "_image_size")
3840 if not image_size:
3841 return None
3842
3843 image_blocks = int(image_size) // 4096 - 1
3844 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3845
3846 # For sparse images, we will only check the blocks that are listed in the care
3847 # map, i.e. the ones with meaningful data.
3848 if "extfs_sparse_flag" in OPTIONS.info_dict:
3849 simg = sparse_img.SparseImage(imgname)
3850 care_map_ranges = simg.care_map.intersect(
3851 rangelib.RangeSet("0-{}".format(image_blocks)))
3852
3853 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3854 # image.
3855 else:
3856 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3857
3858 return [which, care_map_ranges.to_string_raw()]
3859
3860
3861def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3862 """Generates and adds care_map.pb for a/b partition that has care_map.
3863
3864 Args:
3865 output_file: The output zip file (needs to be already open),
3866 or file path to write care_map.pb.
3867 ab_partitions: The list of A/B partitions.
3868 image_paths: A map from the partition name to the image path.
3869 """
3870 if not output_file:
3871 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3872
3873 care_map_list = []
3874 for partition in ab_partitions:
3875 partition = partition.strip()
3876 if partition not in PARTITIONS_WITH_CARE_MAP:
3877 continue
3878
3879 verity_block_device = "{}_verity_block_device".format(partition)
3880 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3881 if (verity_block_device in OPTIONS.info_dict or
3882 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3883 if partition not in image_paths:
3884 logger.warning('Potential partition with care_map missing from images: %s',
3885 partition)
3886 continue
3887 image_path = image_paths[partition]
3888 if not os.path.exists(image_path):
3889 raise ExternalError('Expected image at path {}'.format(image_path))
3890
3891 care_map = GetCareMap(partition, image_path)
3892 if not care_map:
3893 continue
3894 care_map_list += care_map
3895
3896 # adds fingerprint field to the care_map
3897 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3898 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3899 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3900 "ro.{}.build.thumbprint".format(partition)]
3901
3902 present_props = [x for x in prop_name_list if
3903 partition_props and partition_props.GetProp(x)]
3904 if not present_props:
3905 logger.warning(
3906 "fingerprint is not present for partition %s", partition)
3907 property_id, fingerprint = "unknown", "unknown"
3908 else:
3909 property_id = present_props[0]
3910 fingerprint = partition_props.GetProp(property_id)
3911 care_map_list += [property_id, fingerprint]
3912
3913 if not care_map_list:
3914 return
3915
3916 # Converts the list into proto buf message by calling care_map_generator; and
3917 # writes the result to a temp file.
3918 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3919 suffix=".txt")
3920 with open(temp_care_map_text, 'w') as text_file:
3921 text_file.write('\n'.join(care_map_list))
3922
3923 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3924 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3925 RunAndCheckOutput(care_map_gen_cmd)
3926
3927 if not isinstance(output_file, zipfile.ZipFile):
3928 shutil.copy(temp_care_map, output_file)
3929 return
3930 # output_file is a zip file
3931 care_map_path = "META/care_map.pb"
3932 if care_map_path in output_file.namelist():
3933 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3934 # replace_updated_files_list used by add_img_to_target_files
3935 if not OPTIONS.replace_updated_files_list:
3936 OPTIONS.replace_updated_files_list = []
3937 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3938 OPTIONS.replace_updated_files_list.append(care_map_path)
3939 else:
3940 ZipWrite(output_file, temp_care_map, arcname=care_map_path)