blob: 995b0450291924cd5be2d7a2f66ae2b4c0669d72 [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 = []
Tianjie0f307452020-04-01 12:20:21 -070083 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080084 self.aftl_server = None
85 self.aftl_key_path = None
86 self.aftl_manufacturer_key_path = None
87 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070088 self.verbose = False
89 self.tempfiles = []
90 self.device_specific = None
91 self.extras = {}
92 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070093 self.source_info_dict = None
94 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070095 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070096 # Stash size cannot exceed cache_size * threshold.
97 self.cache_size = None
98 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070099 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -0700100 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700101
102
103OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700104
Tao Bao71197512018-10-11 14:08:45 -0700105# The block size that's used across the releasetools scripts.
106BLOCK_SIZE = 4096
107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800108# Values for "certificate" in apkcerts that mean special things.
109SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
110
Tao Bao5cc0abb2019-03-21 10:18:05 -0700111# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
112# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800113# descriptor into vbmeta.img. When adding a new entry here, the
114# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
115# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000116AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
117 'system', 'system_ext', 'vendor', 'vendor_boot',
118 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800119
Tao Bao08c190f2019-06-03 23:07:58 -0700120# Chained VBMeta partitions.
121AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
122
Tianjie Xu861f4132018-09-12 11:49:33 -0700123# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400124PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700125 'system',
126 'vendor',
127 'product',
128 'system_ext',
129 'odm',
130 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700131 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Kelvin Zhang563750f2021-04-28 12:46:17 -0400141
Tianjie Xu209db462016-05-24 17:34:52 -0700142class ErrorCode(object):
143 """Define error_codes for failures that happen during the actual
144 update package installation.
145
146 Error codes 0-999 are reserved for failures before the package
147 installation (i.e. low battery, package verification failure).
148 Detailed code in 'bootable/recovery/error_code.h' """
149
150 SYSTEM_VERIFICATION_FAILURE = 1000
151 SYSTEM_UPDATE_FAILURE = 1001
152 SYSTEM_UNEXPECTED_CONTENTS = 1002
153 SYSTEM_NONZERO_CONTENTS = 1003
154 SYSTEM_RECOVER_FAILURE = 1004
155 VENDOR_VERIFICATION_FAILURE = 2000
156 VENDOR_UPDATE_FAILURE = 2001
157 VENDOR_UNEXPECTED_CONTENTS = 2002
158 VENDOR_NONZERO_CONTENTS = 2003
159 VENDOR_RECOVER_FAILURE = 2004
160 OEM_PROP_MISMATCH = 3000
161 FINGERPRINT_MISMATCH = 3001
162 THUMBPRINT_MISMATCH = 3002
163 OLDER_BUILD = 3003
164 DEVICE_MISMATCH = 3004
165 BAD_PATCH_FILE = 3005
166 INSUFFICIENT_CACHE_SPACE = 3006
167 TUNE_PARTITION_FAILURE = 3007
168 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800169
Tao Bao80921982018-03-21 21:02:19 -0700170
Dan Albert8b72aef2015-03-23 19:13:21 -0700171class ExternalError(RuntimeError):
172 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700173
174
Tao Bao32fcdab2018-10-12 10:30:39 -0700175def InitLogging():
176 DEFAULT_LOGGING_CONFIG = {
177 'version': 1,
178 'disable_existing_loggers': False,
179 'formatters': {
180 'standard': {
181 'format':
182 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
183 'datefmt': '%Y-%m-%d %H:%M:%S',
184 },
185 },
186 'handlers': {
187 'default': {
188 'class': 'logging.StreamHandler',
189 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700190 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700191 },
192 },
193 'loggers': {
194 '': {
195 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700196 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700197 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 }
199 }
200 }
201 env_config = os.getenv('LOGGING_CONFIG')
202 if env_config:
203 with open(env_config) as f:
204 config = json.load(f)
205 else:
206 config = DEFAULT_LOGGING_CONFIG
207
208 # Increase the logging level for verbose mode.
209 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700210 config = copy.deepcopy(config)
211 config['handlers']['default']['level'] = 'INFO'
212
213 if OPTIONS.logfile:
214 config = copy.deepcopy(config)
215 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400216 'class': 'logging.FileHandler',
217 'formatter': 'standard',
218 'level': 'INFO',
219 'mode': 'w',
220 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700221 }
222 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700223
224 logging.config.dictConfig(config)
225
226
Yifan Hong8e332ff2020-07-29 17:51:55 -0700227def SetHostToolLocation(tool_name, location):
228 OPTIONS.host_tools[tool_name] = location
229
Kelvin Zhang563750f2021-04-28 12:46:17 -0400230
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900231def FindHostToolPath(tool_name):
232 """Finds the path to the host tool.
233
234 Args:
235 tool_name: name of the tool to find
236 Returns:
237 path to the tool if found under either one of the host_tools map or under
238 the same directory as this binary is located at. If not found, tool_name
239 is returned.
240 """
241 if tool_name in OPTIONS.host_tools:
242 return OPTIONS.host_tools[tool_name]
243
244 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
245 tool_path = os.path.join(my_dir, tool_name)
246 if os.path.exists(tool_path):
247 return tool_path
248
249 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700250
Kelvin Zhang563750f2021-04-28 12:46:17 -0400251
Tao Bao39451582017-05-04 11:10:47 -0700252def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700253 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700254
Tao Bao73dd4f42018-10-04 16:25:33 -0700255 Args:
256 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700257 verbose: Whether the commands should be shown. Default to the global
258 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700259 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
260 stdin, etc. stdout and stderr will default to subprocess.PIPE and
261 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800262 universal_newlines will default to True, as most of the users in
263 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700264
265 Returns:
266 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700267 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700268 if 'stdout' not in kwargs and 'stderr' not in kwargs:
269 kwargs['stdout'] = subprocess.PIPE
270 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800271 if 'universal_newlines' not in kwargs:
272 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 if args:
275 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700276 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900277 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700278
Tao Bao32fcdab2018-10-12 10:30:39 -0700279 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400280 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700281 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700282 return subprocess.Popen(args, **kwargs)
283
284
Tao Bao986ee862018-10-04 15:46:16 -0700285def RunAndCheckOutput(args, verbose=None, **kwargs):
286 """Runs the given command and returns the output.
287
288 Args:
289 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700290 verbose: Whether the commands should be shown. Default to the global
291 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700292 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
293 stdin, etc. stdout and stderr will default to subprocess.PIPE and
294 subprocess.STDOUT respectively unless caller specifies any of them.
295
296 Returns:
297 The output string.
298
299 Raises:
300 ExternalError: On non-zero exit from the command.
301 """
Tao Bao986ee862018-10-04 15:46:16 -0700302 proc = Run(args, verbose=verbose, **kwargs)
303 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800304 if output is None:
305 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700306 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400307 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700308 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700309 if proc.returncode != 0:
310 raise ExternalError(
311 "Failed to run command '{}' (exit code {}):\n{}".format(
312 args, proc.returncode, output))
313 return output
314
315
Tao Baoc765cca2018-01-31 17:32:40 -0800316def RoundUpTo4K(value):
317 rounded_up = value + 4095
318 return rounded_up - (rounded_up % 4096)
319
320
Ying Wang7e6d4e42010-12-13 16:25:36 -0800321def CloseInheritedPipes():
322 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
323 before doing other work."""
324 if platform.system() != "Darwin":
325 return
326 for d in range(3, 1025):
327 try:
328 stat = os.fstat(d)
329 if stat is not None:
330 pipebit = stat[0] & 0x1000
331 if pipebit != 0:
332 os.close(d)
333 except OSError:
334 pass
335
336
Tao Bao1c320f82019-10-04 23:25:12 -0700337class BuildInfo(object):
338 """A class that holds the information for a given build.
339
340 This class wraps up the property querying for a given source or target build.
341 It abstracts away the logic of handling OEM-specific properties, and caches
342 the commonly used properties such as fingerprint.
343
344 There are two types of info dicts: a) build-time info dict, which is generated
345 at build time (i.e. included in a target_files zip); b) OEM info dict that is
346 specified at package generation time (via command line argument
347 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
348 having "oem_fingerprint_properties" in build-time info dict), all the queries
349 would be answered based on build-time info dict only. Otherwise if using
350 OEM-specific properties, some of them will be calculated from two info dicts.
351
352 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800353 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700354
355 Attributes:
356 info_dict: The build-time info dict.
357 is_ab: Whether it's a build that uses A/B OTA.
358 oem_dicts: A list of OEM dicts.
359 oem_props: A list of OEM properties that should be read from OEM dicts; None
360 if the build doesn't use any OEM-specific property.
361 fingerprint: The fingerprint of the build, which would be calculated based
362 on OEM properties if applicable.
363 device: The device name, which could come from OEM dicts if applicable.
364 """
365
366 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
367 "ro.product.manufacturer", "ro.product.model",
368 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700369 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
370 "product", "odm", "vendor", "system_ext", "system"]
371 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
372 "product", "product_services", "odm", "vendor", "system"]
373 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700374
Tianjiefdda51d2021-05-05 14:46:35 -0700375 # The length of vbmeta digest to append to the fingerprint
376 _VBMETA_DIGEST_SIZE_USED = 8
377
378 def __init__(self, info_dict, oem_dicts=None, use_legacy_id=False):
Tao Bao1c320f82019-10-04 23:25:12 -0700379 """Initializes a BuildInfo instance with the given dicts.
380
381 Note that it only wraps up the given dicts, without making copies.
382
383 Arguments:
384 info_dict: The build-time info dict.
385 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
386 that it always uses the first dict to calculate the fingerprint or the
387 device name. The rest would be used for asserting OEM properties only
388 (e.g. one package can be installed on one of these devices).
Tianjiefdda51d2021-05-05 14:46:35 -0700389 use_legacy_id: Use the legacy build id to construct the fingerprint. This
390 is used when we need a BuildInfo class, while the vbmeta digest is
391 unavailable.
Tao Bao1c320f82019-10-04 23:25:12 -0700392
393 Raises:
394 ValueError: On invalid inputs.
395 """
396 self.info_dict = info_dict
397 self.oem_dicts = oem_dicts
398
399 self._is_ab = info_dict.get("ab_update") == "true"
Tianjiefdda51d2021-05-05 14:46:35 -0700400 self.use_legacy_id = use_legacy_id
Tao Bao1c320f82019-10-04 23:25:12 -0700401
Hongguang Chend7c160f2020-05-03 21:24:26 -0700402 # Skip _oem_props if oem_dicts is None to use BuildInfo in
403 # sign_target_files_apks
404 if self.oem_dicts:
405 self._oem_props = info_dict.get("oem_fingerprint_properties")
406 else:
407 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700408
Daniel Normand5fe8622020-01-08 17:01:11 -0800409 def check_fingerprint(fingerprint):
410 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
411 raise ValueError(
412 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
413 "3.2.2. Build Parameters.".format(fingerprint))
414
Daniel Normand5fe8622020-01-08 17:01:11 -0800415 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800416 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800417 try:
418 fingerprint = self.CalculatePartitionFingerprint(partition)
419 check_fingerprint(fingerprint)
420 self._partition_fingerprints[partition] = fingerprint
421 except ExternalError:
422 continue
423 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800424 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800425 # need a fingerprint when creating the image.
426 self._partition_fingerprints[
427 "system_other"] = self._partition_fingerprints["system"]
428
Tao Bao1c320f82019-10-04 23:25:12 -0700429 # These two should be computed only after setting self._oem_props.
Steve Kondik8a6df782010-04-21 11:39:48 -0400430 self._device = info_dict.get("ota_override_device", self.GetOemProperty("ro.product.device"))
Tao Bao1c320f82019-10-04 23:25:12 -0700431 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800432 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700433
434 @property
435 def is_ab(self):
436 return self._is_ab
437
438 @property
439 def device(self):
440 return self._device
441
442 @property
443 def fingerprint(self):
444 return self._fingerprint
445
446 @property
Kelvin Zhang563750f2021-04-28 12:46:17 -0400447 def is_vabc(self):
448 vendor_prop = self.info_dict.get("vendor.build.prop")
449 vabc_enabled = vendor_prop and \
450 vendor_prop.GetProp("ro.virtual_ab.compression.enabled") == "true"
451 return vabc_enabled
452
453 @property
Kelvin Zhang9b558852021-06-10 14:32:19 -0400454 def vendor_suppressed_vabc(self):
455 vendor_prop = self.info_dict.get("vendor.build.prop")
456 vabc_suppressed = vendor_prop and \
457 vendor_prop.GetProp("ro.vendor.build.dont_use_vabc")
458 return vabc_suppressed and vabc_suppressed.lower() == "true"
459
460 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700461 def oem_props(self):
462 return self._oem_props
463
464 def __getitem__(self, key):
465 return self.info_dict[key]
466
467 def __setitem__(self, key, value):
468 self.info_dict[key] = value
469
470 def get(self, key, default=None):
471 return self.info_dict.get(key, default)
472
473 def items(self):
474 return self.info_dict.items()
475
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000476 def _GetRawBuildProp(self, prop, partition):
477 prop_file = '{}.build.prop'.format(
478 partition) if partition else 'build.prop'
479 partition_props = self.info_dict.get(prop_file)
480 if not partition_props:
481 return None
482 return partition_props.GetProp(prop)
483
Daniel Normand5fe8622020-01-08 17:01:11 -0800484 def GetPartitionBuildProp(self, prop, partition):
485 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800486
487 # Boot image uses ro.[product.]bootimage instead of boot.
Kelvin Zhang563750f2021-04-28 12:46:17 -0400488 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800489
Daniel Normand5fe8622020-01-08 17:01:11 -0800490 # If provided a partition for this property, only look within that
491 # partition's build.prop.
492 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800493 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800494 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800495 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000496
497 prop_val = self._GetRawBuildProp(prop, partition)
498 if prop_val is not None:
499 return prop_val
500 raise ExternalError("couldn't find %s in %s.build.prop" %
501 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800502
Tao Bao1c320f82019-10-04 23:25:12 -0700503 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800504 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700505 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
506 return self._ResolveRoProductBuildProp(prop)
507
Tianjiefdda51d2021-05-05 14:46:35 -0700508 if prop == "ro.build.id":
509 return self._GetBuildId()
510
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000511 prop_val = self._GetRawBuildProp(prop, None)
512 if prop_val is not None:
513 return prop_val
514
515 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700516
517 def _ResolveRoProductBuildProp(self, prop):
518 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000519 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700520 if prop_val:
521 return prop_val
522
Steven Laver8e2086e2020-04-27 16:26:31 -0700523 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000524 source_order_val = self._GetRawBuildProp(
525 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700526 if source_order_val:
527 source_order = source_order_val.split(",")
528 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700529 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700530
531 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700532 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700533 raise ExternalError(
534 "Invalid ro.product.property_source_order '{}'".format(source_order))
535
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000536 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700537 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000538 "ro.product", "ro.product.{}".format(source_partition), 1)
539 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700540 if prop_val:
541 return prop_val
542
543 raise ExternalError("couldn't resolve {}".format(prop))
544
Steven Laver8e2086e2020-04-27 16:26:31 -0700545 def _GetRoProductPropsDefaultSourceOrder(self):
546 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
547 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000548 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700549 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000550 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700551 if android_version == "10":
552 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
553 # NOTE: float() conversion of android_version will have rounding error.
554 # We are checking for "9" or less, and using "< 10" is well outside of
555 # possible floating point rounding.
556 try:
557 android_version_val = float(android_version)
558 except ValueError:
559 android_version_val = 0
560 if android_version_val < 10:
561 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
562 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
563
Tianjieb37c5be2020-10-15 21:27:10 -0700564 def _GetPlatformVersion(self):
565 version_sdk = self.GetBuildProp("ro.build.version.sdk")
566 # init code switches to version_release_or_codename (see b/158483506). After
567 # API finalization, release_or_codename will be the same as release. This
568 # is the best effort to support pre-S dev stage builds.
569 if int(version_sdk) >= 30:
570 try:
571 return self.GetBuildProp("ro.build.version.release_or_codename")
572 except ExternalError:
573 logger.warning('Failed to find ro.build.version.release_or_codename')
574
575 return self.GetBuildProp("ro.build.version.release")
576
Tianjiefdda51d2021-05-05 14:46:35 -0700577 def _GetBuildId(self):
578 build_id = self._GetRawBuildProp("ro.build.id", None)
579 if build_id:
580 return build_id
581
582 legacy_build_id = self.GetBuildProp("ro.build.legacy.id")
583 if not legacy_build_id:
584 raise ExternalError("Couldn't find build id in property file")
585
586 if self.use_legacy_id:
587 return legacy_build_id
588
589 # Append the top 8 chars of vbmeta digest to the existing build id. The
590 # logic needs to match the one in init, so that OTA can deliver correctly.
591 avb_enable = self.info_dict.get("avb_enable") == "true"
592 if not avb_enable:
593 raise ExternalError("AVB isn't enabled when using legacy build id")
594
595 vbmeta_digest = self.info_dict.get("vbmeta_digest")
596 if not vbmeta_digest:
597 raise ExternalError("Vbmeta digest isn't provided when using legacy build"
598 " id")
599 if len(vbmeta_digest) < self._VBMETA_DIGEST_SIZE_USED:
600 raise ExternalError("Invalid vbmeta digest " + vbmeta_digest)
601
602 digest_prefix = vbmeta_digest[:self._VBMETA_DIGEST_SIZE_USED]
603 return legacy_build_id + '.' + digest_prefix
604
Tianjieb37c5be2020-10-15 21:27:10 -0700605 def _GetPartitionPlatformVersion(self, partition):
606 try:
607 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
608 partition)
609 except ExternalError:
610 return self.GetPartitionBuildProp("ro.build.version.release",
611 partition)
612
Tao Bao1c320f82019-10-04 23:25:12 -0700613 def GetOemProperty(self, key):
614 if self.oem_props is not None and key in self.oem_props:
615 return self.oem_dicts[0][key]
616 return self.GetBuildProp(key)
617
Daniel Normand5fe8622020-01-08 17:01:11 -0800618 def GetPartitionFingerprint(self, partition):
619 return self._partition_fingerprints.get(partition, None)
620
621 def CalculatePartitionFingerprint(self, partition):
622 try:
623 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
624 except ExternalError:
625 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
626 self.GetPartitionBuildProp("ro.product.brand", partition),
627 self.GetPartitionBuildProp("ro.product.name", partition),
628 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700629 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800630 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400631 self.GetPartitionBuildProp(
632 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800633 self.GetPartitionBuildProp("ro.build.type", partition),
634 self.GetPartitionBuildProp("ro.build.tags", partition))
635
Tao Bao1c320f82019-10-04 23:25:12 -0700636 def CalculateFingerprint(self):
637 if self.oem_props is None:
638 try:
639 return self.GetBuildProp("ro.build.fingerprint")
640 except ExternalError:
641 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
642 self.GetBuildProp("ro.product.brand"),
643 self.GetBuildProp("ro.product.name"),
644 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700645 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700646 self.GetBuildProp("ro.build.id"),
647 self.GetBuildProp("ro.build.version.incremental"),
648 self.GetBuildProp("ro.build.type"),
649 self.GetBuildProp("ro.build.tags"))
650 return "%s/%s/%s:%s" % (
651 self.GetOemProperty("ro.product.brand"),
652 self.GetOemProperty("ro.product.name"),
653 self.GetOemProperty("ro.product.device"),
654 self.GetBuildProp("ro.build.thumbprint"))
655
656 def WriteMountOemScript(self, script):
657 assert self.oem_props is not None
658 recovery_mount_options = self.info_dict.get("recovery_mount_options")
659 script.Mount("/oem", recovery_mount_options)
660
661 def WriteDeviceAssertions(self, script, oem_no_mount):
662 # Read the property directly if not using OEM properties.
663 if not self.oem_props:
664 script.AssertDevice(self.device)
665 return
666
667 # Otherwise assert OEM properties.
668 if not self.oem_dicts:
669 raise ExternalError(
670 "No OEM file provided to answer expected assertions")
671
672 for prop in self.oem_props.split():
673 values = []
674 for oem_dict in self.oem_dicts:
675 if prop in oem_dict:
676 values.append(oem_dict[prop])
677 if not values:
678 raise ExternalError(
679 "The OEM file is missing the property %s" % (prop,))
680 script.AssertOemProperty(prop, values, oem_no_mount)
681
682
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000683def ReadFromInputFile(input_file, fn):
684 """Reads the contents of fn from input zipfile or directory."""
685 if isinstance(input_file, zipfile.ZipFile):
686 return input_file.read(fn).decode()
687 else:
688 path = os.path.join(input_file, *fn.split("/"))
689 try:
690 with open(path) as f:
691 return f.read()
692 except IOError as e:
693 if e.errno == errno.ENOENT:
694 raise KeyError(fn)
695
696
Yifan Hong10482a22021-01-07 14:38:41 -0800697def ExtractFromInputFile(input_file, fn):
698 """Extracts the contents of fn from input zipfile or directory into a file."""
699 if isinstance(input_file, zipfile.ZipFile):
700 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500701 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800702 f.write(input_file.read(fn))
703 return tmp_file
704 else:
705 file = os.path.join(input_file, *fn.split("/"))
706 if not os.path.exists(file):
707 raise KeyError(fn)
708 return file
709
Kelvin Zhang563750f2021-04-28 12:46:17 -0400710
jiajia tangf3f842b2021-03-17 21:49:44 +0800711class RamdiskFormat(object):
712 LZ4 = 1
713 GZ = 2
Luca Stefani31b6bee2020-06-11 13:03:18 +0200714 XZ = 3
Yifan Hong10482a22021-01-07 14:38:41 -0800715
Kelvin Zhang563750f2021-04-28 12:46:17 -0400716
jiajia tang836f76b2021-04-02 14:48:26 +0800717def _GetRamdiskFormat(info_dict):
718 if info_dict.get('lz4_ramdisks') == 'true':
719 ramdisk_format = RamdiskFormat.LZ4
Luca Stefani31b6bee2020-06-11 13:03:18 +0200720 if info_dict.get('xz_ramdisks') == 'true':
721 ramdisk_format = RamdiskFormat.XZ
jiajia tang836f76b2021-04-02 14:48:26 +0800722 else:
723 ramdisk_format = RamdiskFormat.GZ
724 return ramdisk_format
725
Kelvin Zhang563750f2021-04-28 12:46:17 -0400726
Tao Bao410ad8b2018-08-24 12:08:38 -0700727def LoadInfoDict(input_file, repacking=False):
728 """Loads the key/value pairs from the given input target_files.
729
Tianjiea85bdf02020-07-29 11:56:19 -0700730 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700731 checks and returns the parsed key/value pairs for to the given build. It's
732 usually called early when working on input target_files files, e.g. when
733 generating OTAs, or signing builds. Note that the function may be called
734 against an old target_files file (i.e. from past dessert releases). So the
735 property parsing needs to be backward compatible.
736
737 In a `META/misc_info.txt`, a few properties are stored as links to the files
738 in the PRODUCT_OUT directory. It works fine with the build system. However,
739 they are no longer available when (re)generating images from target_files zip.
740 When `repacking` is True, redirect these properties to the actual files in the
741 unzipped directory.
742
743 Args:
744 input_file: The input target_files file, which could be an open
745 zipfile.ZipFile instance, or a str for the dir that contains the files
746 unzipped from a target_files file.
747 repacking: Whether it's trying repack an target_files file after loading the
748 info dict (default: False). If so, it will rewrite a few loaded
749 properties (e.g. selinux_fc, root_dir) to point to the actual files in
750 target_files file. When doing repacking, `input_file` must be a dir.
751
752 Returns:
753 A dict that contains the parsed key/value pairs.
754
755 Raises:
756 AssertionError: On invalid input arguments.
757 ValueError: On malformed input values.
758 """
759 if repacking:
760 assert isinstance(input_file, str), \
761 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700762
Doug Zongkerc9253822014-02-04 12:17:58 -0800763 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000764 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800765
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700766 try:
Michael Runge6e836112014-04-15 17:40:21 -0700767 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700768 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700769 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700770
Tao Bao410ad8b2018-08-24 12:08:38 -0700771 if "recovery_api_version" not in d:
772 raise ValueError("Failed to find 'recovery_api_version'")
773 if "fstab_version" not in d:
774 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800775
Tao Bao410ad8b2018-08-24 12:08:38 -0700776 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700777 # "selinux_fc" properties should point to the file_contexts files
778 # (file_contexts.bin) under META/.
779 for key in d:
780 if key.endswith("selinux_fc"):
781 fc_basename = os.path.basename(d[key])
782 fc_config = os.path.join(input_file, "META", fc_basename)
783 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700784
Daniel Norman72c626f2019-05-13 15:58:14 -0700785 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700786
Tom Cherryd14b8952018-08-09 14:26:00 -0700787 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700788 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700789 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700790 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700791
David Anderson0ec64ac2019-12-06 12:21:18 -0800792 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700793 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700794 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800795 key_name = part_name + "_base_fs_file"
796 if key_name not in d:
797 continue
798 basename = os.path.basename(d[key_name])
799 base_fs_file = os.path.join(input_file, "META", basename)
800 if os.path.exists(base_fs_file):
801 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700802 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700803 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800804 "Failed to find %s base fs file: %s", part_name, base_fs_file)
805 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700806
Doug Zongker37974732010-09-16 17:44:38 -0700807 def makeint(key):
808 if key in d:
809 d[key] = int(d[key], 0)
810
811 makeint("recovery_api_version")
812 makeint("blocksize")
813 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700814 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700815 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700816 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700817 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800818 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700819
Steve Muckle903a1ca2020-05-07 17:32:10 -0700820 boot_images = "boot.img"
821 if "boot_images" in d:
822 boot_images = d["boot_images"]
823 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400824 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700825
Tao Bao765668f2019-10-04 22:03:00 -0700826 # Load recovery fstab if applicable.
827 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800828 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800829
Tianjie Xu861f4132018-09-12 11:49:33 -0700830 # Tries to load the build props for all partitions with care_map, including
831 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800832 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800833 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000834 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800835 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700836 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800837
Tao Bao3ed35d32019-10-07 20:48:48 -0700838 # Set up the salt (based on fingerprint) that will be used when adding AVB
839 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800840 if d.get("avb_enable") == "true":
Tianjiefdda51d2021-05-05 14:46:35 -0700841 build_info = BuildInfo(d, use_legacy_id=True)
Yifan Hong5057b952021-01-07 14:09:57 -0800842 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800843 fingerprint = build_info.GetPartitionFingerprint(partition)
844 if fingerprint:
Kelvin Zhang563750f2021-04-28 12:46:17 -0400845 d["avb_{}_salt".format(partition)] = sha256(
846 fingerprint.encode()).hexdigest()
Tianjiefdda51d2021-05-05 14:46:35 -0700847
848 # Set the vbmeta digest if exists
849 try:
850 d["vbmeta_digest"] = read_helper("META/vbmeta_digest.txt").rstrip()
851 except KeyError:
852 pass
853
Kelvin Zhang39aea442020-08-17 11:04:25 -0400854 try:
855 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
856 except KeyError:
857 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700858 return d
859
Tao Baod1de6f32017-03-01 16:38:48 -0800860
Daniel Norman4cc9df62019-07-18 10:11:07 -0700861def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900862 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700863 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900864
Daniel Norman4cc9df62019-07-18 10:11:07 -0700865
866def LoadDictionaryFromFile(file_path):
867 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900868 return LoadDictionaryFromLines(lines)
869
870
Michael Runge6e836112014-04-15 17:40:21 -0700871def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700872 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700873 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700874 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700875 if not line or line.startswith("#"):
876 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700877 if "=" in line:
878 name, value = line.split("=", 1)
879 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700880 return d
881
Tao Baod1de6f32017-03-01 16:38:48 -0800882
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000883class PartitionBuildProps(object):
884 """The class holds the build prop of a particular partition.
885
886 This class loads the build.prop and holds the build properties for a given
887 partition. It also partially recognizes the 'import' statement in the
888 build.prop; and calculates alternative values of some specific build
889 properties during runtime.
890
891 Attributes:
892 input_file: a zipped target-file or an unzipped target-file directory.
893 partition: name of the partition.
894 props_allow_override: a list of build properties to search for the
895 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000896 build_props: a dict of build properties for the given partition.
897 prop_overrides: a set of props that are overridden by import.
898 placeholder_values: A dict of runtime variables' values to replace the
899 placeholders in the build.prop file. We expect exactly one value for
900 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800901 ramdisk_format: If name is "boot", the format of ramdisk inside the
902 boot image. Otherwise, its value is ignored.
903 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000904 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400905
Tianjie Xu9afb2212020-05-10 21:48:15 +0000906 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000907 self.input_file = input_file
908 self.partition = name
909 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000910 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000911 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000912 self.prop_overrides = set()
913 self.placeholder_values = {}
914 if placeholder_values:
915 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000916
917 @staticmethod
918 def FromDictionary(name, build_props):
919 """Constructs an instance from a build prop dictionary."""
920
921 props = PartitionBuildProps("unknown", name)
922 props.build_props = build_props.copy()
923 return props
924
925 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800926 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000927 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800928
929 if name == "boot":
Kelvin Zhang563750f2021-04-28 12:46:17 -0400930 data = PartitionBuildProps._ReadBootPropFile(
931 input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800932 else:
933 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
934
935 props = PartitionBuildProps(input_file, name, placeholder_values)
936 props._LoadBuildProp(data)
937 return props
938
939 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800940 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800941 """
942 Read build.prop for boot image from input_file.
943 Return empty string if not found.
944 """
945 try:
946 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
947 except KeyError:
948 logger.warning('Failed to read IMAGES/boot.img')
949 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800950 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800951 if prop_file is None:
952 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500953 with open(prop_file, "r") as f:
954 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800955
956 @staticmethod
957 def _ReadPartitionPropFile(input_file, name):
958 """
959 Read build.prop for name from input_file.
960 Return empty string if not found.
961 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000962 data = ''
963 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
964 '{}/build.prop'.format(name.upper())]:
965 try:
966 data = ReadFromInputFile(input_file, prop_file)
967 break
968 except KeyError:
969 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800970 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000971
Yifan Hong125d0b62020-09-24 17:07:03 -0700972 @staticmethod
973 def FromBuildPropFile(name, build_prop_file):
974 """Constructs an instance from a build prop file."""
975
976 props = PartitionBuildProps("unknown", name)
977 with open(build_prop_file) as f:
978 props._LoadBuildProp(f.read())
979 return props
980
Tianjie Xu9afb2212020-05-10 21:48:15 +0000981 def _LoadBuildProp(self, data):
982 for line in data.split('\n'):
983 line = line.strip()
984 if not line or line.startswith("#"):
985 continue
986 if line.startswith("import"):
987 overrides = self._ImportParser(line)
988 duplicates = self.prop_overrides.intersection(overrides.keys())
989 if duplicates:
990 raise ValueError('prop {} is overridden multiple times'.format(
991 ','.join(duplicates)))
992 self.prop_overrides = self.prop_overrides.union(overrides.keys())
993 self.build_props.update(overrides)
994 elif "=" in line:
995 name, value = line.split("=", 1)
996 if name in self.prop_overrides:
997 raise ValueError('prop {} is set again after overridden by import '
998 'statement'.format(name))
999 self.build_props[name] = value
1000
1001 def _ImportParser(self, line):
1002 """Parses the build prop in a given import statement."""
1003
1004 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -04001005 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +00001006 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -07001007
1008 if len(tokens) == 3:
1009 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
1010 return {}
1011
Tianjie Xu9afb2212020-05-10 21:48:15 +00001012 import_path = tokens[1]
1013 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
1014 raise ValueError('Unrecognized import path {}'.format(line))
1015
1016 # We only recognize a subset of import statement that the init process
1017 # supports. And we can loose the restriction based on how the dynamic
1018 # fingerprint is used in practice. The placeholder format should be
1019 # ${placeholder}, and its value should be provided by the caller through
1020 # the placeholder_values.
1021 for prop, value in self.placeholder_values.items():
1022 prop_place_holder = '${{{}}}'.format(prop)
1023 if prop_place_holder in import_path:
1024 import_path = import_path.replace(prop_place_holder, value)
1025 if '$' in import_path:
1026 logger.info('Unresolved place holder in import path %s', import_path)
1027 return {}
1028
1029 import_path = import_path.replace('/{}'.format(self.partition),
1030 self.partition.upper())
1031 logger.info('Parsing build props override from %s', import_path)
1032
1033 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
1034 d = LoadDictionaryFromLines(lines)
1035 return {key: val for key, val in d.items()
1036 if key in self.props_allow_override}
1037
Tianjie Xu0fde41e2020-05-09 05:24:18 +00001038 def GetProp(self, prop):
1039 return self.build_props.get(prop)
1040
1041
Tianjie Xucfa86222016-03-07 16:31:19 -08001042def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
1043 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001044 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -07001045 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -07001046 self.mount_point = mount_point
1047 self.fs_type = fs_type
1048 self.device = device
1049 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -07001050 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -07001051 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001052
1053 try:
Tianjie Xucfa86222016-03-07 16:31:19 -08001054 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001055 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001056 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -07001057 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001058
Tao Baod1de6f32017-03-01 16:38:48 -08001059 assert fstab_version == 2
1060
1061 d = {}
1062 for line in data.split("\n"):
1063 line = line.strip()
1064 if not line or line.startswith("#"):
1065 continue
1066
1067 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
1068 pieces = line.split()
1069 if len(pieces) != 5:
1070 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1071
1072 # Ignore entries that are managed by vold.
1073 options = pieces[4]
1074 if "voldmanaged=" in options:
1075 continue
1076
1077 # It's a good line, parse it.
1078 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001079 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001080 options = options.split(",")
1081 for i in options:
1082 if i.startswith("length="):
1083 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001084 elif i == "slotselect":
1085 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001086 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001087 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001088 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001089
Tao Baod1de6f32017-03-01 16:38:48 -08001090 mount_flags = pieces[3]
1091 # Honor the SELinux context if present.
1092 context = None
1093 for i in mount_flags.split(","):
1094 if i.startswith("context="):
1095 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001096
Tao Baod1de6f32017-03-01 16:38:48 -08001097 mount_point = pieces[1]
Brint E. Kriebelebd4d282018-02-14 23:02:06 -08001098 if not d.get(mount_point):
1099 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
1100 device=pieces[0], length=length, context=context,
1101 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001102
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001103 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001104 # system. Other areas assume system is always at "/system" so point /system
1105 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001106 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001107 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001108 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001109 return d
1110
1111
Tao Bao765668f2019-10-04 22:03:00 -07001112def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1113 """Finds the path to recovery fstab and loads its contents."""
1114 # recovery fstab is only meaningful when installing an update via recovery
1115 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001116 if info_dict.get('ab_update') == 'true' and \
1117 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001118 return None
1119
1120 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1121 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1122 # cases, since it may load the info_dict from an old build (e.g. when
1123 # generating incremental OTAs from that build).
1124 system_root_image = info_dict.get('system_root_image') == 'true'
1125 if info_dict.get('no_recovery') != 'true':
1126 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1127 if isinstance(input_file, zipfile.ZipFile):
1128 if recovery_fstab_path not in input_file.namelist():
1129 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1130 else:
1131 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1132 if not os.path.exists(path):
1133 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1134 return LoadRecoveryFSTab(
1135 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1136 system_root_image)
1137
1138 if info_dict.get('recovery_as_boot') == 'true':
1139 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1140 if isinstance(input_file, zipfile.ZipFile):
1141 if recovery_fstab_path not in input_file.namelist():
1142 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1143 else:
1144 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1145 if not os.path.exists(path):
1146 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1147 return LoadRecoveryFSTab(
1148 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1149 system_root_image)
1150
1151 return None
1152
1153
Doug Zongker37974732010-09-16 17:44:38 -07001154def DumpInfoDict(d):
1155 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001156 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001157
Dan Albert8b72aef2015-03-23 19:13:21 -07001158
Daniel Norman55417142019-11-25 16:04:36 -08001159def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001160 """Merges dynamic partition info variables.
1161
1162 Args:
1163 framework_dict: The dictionary of dynamic partition info variables from the
1164 partial framework target files.
1165 vendor_dict: The dictionary of dynamic partition info variables from the
1166 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001167
1168 Returns:
1169 The merged dynamic partition info dictionary.
1170 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001171
1172 def uniq_concat(a, b):
1173 combined = set(a.split(" "))
1174 combined.update(set(b.split(" ")))
1175 combined = [item.strip() for item in combined if item.strip()]
1176 return " ".join(sorted(combined))
1177
1178 if (framework_dict.get("use_dynamic_partitions") !=
Kelvin Zhang563750f2021-04-28 12:46:17 -04001179 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001180 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1181
1182 merged_dict = {"use_dynamic_partitions": "true"}
1183
1184 merged_dict["dynamic_partition_list"] = uniq_concat(
1185 framework_dict.get("dynamic_partition_list", ""),
1186 vendor_dict.get("dynamic_partition_list", ""))
1187
1188 # Super block devices are defined by the vendor dict.
1189 if "super_block_devices" in vendor_dict:
1190 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1191 for block_device in merged_dict["super_block_devices"].split(" "):
1192 key = "super_%s_device_size" % block_device
1193 if key not in vendor_dict:
1194 raise ValueError("Vendor dict does not contain required key %s." % key)
1195 merged_dict[key] = vendor_dict[key]
1196
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001197 # Partition groups and group sizes are defined by the vendor dict because
1198 # these values may vary for each board that uses a shared system image.
1199 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001200 for partition_group in merged_dict["super_partition_groups"].split(" "):
1201 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001202 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001203 if key not in vendor_dict:
1204 raise ValueError("Vendor dict does not contain required key %s." % key)
1205 merged_dict[key] = vendor_dict[key]
1206
1207 # Set the partition group's partition list using a concatenation of the
1208 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001209 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001210 merged_dict[key] = uniq_concat(
1211 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301212
Daniel Normanb0c75912020-09-24 14:30:21 -07001213 # Various other flags should be copied from the vendor dict, if defined.
1214 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1215 "super_metadata_device", "super_partition_error_limit",
1216 "super_partition_size"):
1217 if key in vendor_dict.keys():
1218 merged_dict[key] = vendor_dict[key]
1219
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001220 return merged_dict
1221
1222
Daniel Norman21c34f72020-11-11 17:25:50 -08001223def PartitionMapFromTargetFiles(target_files_dir):
1224 """Builds a map from partition -> path within an extracted target files directory."""
1225 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1226 possible_subdirs = {
1227 "system": ["SYSTEM"],
1228 "vendor": ["VENDOR", "SYSTEM/vendor"],
1229 "product": ["PRODUCT", "SYSTEM/product"],
1230 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1231 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1232 "vendor_dlkm": [
1233 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1234 ],
1235 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1236 }
1237 partition_map = {}
1238 for partition, subdirs in possible_subdirs.items():
1239 for subdir in subdirs:
1240 if os.path.exists(os.path.join(target_files_dir, subdir)):
1241 partition_map[partition] = subdir
1242 break
1243 return partition_map
1244
1245
Daniel Normand3351562020-10-29 12:33:11 -07001246def SharedUidPartitionViolations(uid_dict, partition_groups):
1247 """Checks for APK sharedUserIds that cross partition group boundaries.
1248
1249 This uses a single or merged build's shareduid_violation_modules.json
1250 output file, as generated by find_shareduid_violation.py or
1251 core/tasks/find-shareduid-violation.mk.
1252
1253 An error is defined as a sharedUserId that is found in a set of partitions
1254 that span more than one partition group.
1255
1256 Args:
1257 uid_dict: A dictionary created by using the standard json module to read a
1258 complete shareduid_violation_modules.json file.
1259 partition_groups: A list of groups, where each group is a list of
1260 partitions.
1261
1262 Returns:
1263 A list of error messages.
1264 """
1265 errors = []
1266 for uid, partitions in uid_dict.items():
1267 found_in_groups = [
1268 group for group in partition_groups
1269 if set(partitions.keys()) & set(group)
1270 ]
1271 if len(found_in_groups) > 1:
1272 errors.append(
1273 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1274 % (uid, ",".join(sorted(partitions.keys()))))
1275 return errors
1276
1277
Daniel Norman21c34f72020-11-11 17:25:50 -08001278def RunHostInitVerifier(product_out, partition_map):
1279 """Runs host_init_verifier on the init rc files within partitions.
1280
1281 host_init_verifier searches the etc/init path within each partition.
1282
1283 Args:
1284 product_out: PRODUCT_OUT directory, containing partition directories.
1285 partition_map: A map of partition name -> relative path within product_out.
1286 """
1287 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1288 cmd = ["host_init_verifier"]
1289 for partition, path in partition_map.items():
1290 if partition not in allowed_partitions:
1291 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1292 partition)
1293 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1294 # Add --property-contexts if the file exists on the partition.
1295 property_contexts = "%s_property_contexts" % (
1296 "plat" if partition == "system" else partition)
1297 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1298 property_contexts)
1299 if os.path.exists(property_contexts_path):
1300 cmd.append("--property-contexts=%s" % property_contexts_path)
1301 # Add the passwd file if the file exists on the partition.
1302 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1303 if os.path.exists(passwd_path):
1304 cmd.extend(["-p", passwd_path])
1305 return RunAndCheckOutput(cmd)
1306
1307
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001308def AppendAVBSigningArgs(cmd, partition):
1309 """Append signing arguments for avbtool."""
1310 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1311 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001312 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1313 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1314 if os.path.exists(new_key_path):
1315 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001316 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1317 if key_path and algorithm:
1318 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001319 avb_salt = OPTIONS.info_dict.get("avb_salt")
1320 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001321 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001322 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001323
1324
Tao Bao765668f2019-10-04 22:03:00 -07001325def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001326 """Returns the VBMeta arguments for partition.
1327
1328 It sets up the VBMeta argument by including the partition descriptor from the
1329 given 'image', or by configuring the partition as a chained partition.
1330
1331 Args:
1332 partition: The name of the partition (e.g. "system").
1333 image: The path to the partition image.
1334 info_dict: A dict returned by common.LoadInfoDict(). Will use
1335 OPTIONS.info_dict if None has been given.
1336
1337 Returns:
1338 A list of VBMeta arguments.
1339 """
1340 if info_dict is None:
1341 info_dict = OPTIONS.info_dict
1342
1343 # Check if chain partition is used.
1344 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001345 if not key_path:
1346 return ["--include_descriptors_from_image", image]
1347
1348 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1349 # into vbmeta.img. The recovery image will be configured on an independent
1350 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1351 # See details at
1352 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001353 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001354 return []
1355
1356 # Otherwise chain the partition into vbmeta.
1357 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1358 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001359
1360
Tao Bao02a08592018-07-22 12:40:45 -07001361def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1362 """Constructs and returns the arg to build or verify a chained partition.
1363
1364 Args:
1365 partition: The partition name.
1366 info_dict: The info dict to look up the key info and rollback index
1367 location.
1368 key: The key to be used for building or verifying the partition. Defaults to
1369 the key listed in info_dict.
1370
1371 Returns:
1372 A string of form "partition:rollback_index_location:key" that can be used to
1373 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001374 """
1375 if key is None:
1376 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001377 if key and not os.path.exists(key) and OPTIONS.search_path:
1378 new_key_path = os.path.join(OPTIONS.search_path, key)
1379 if os.path.exists(new_key_path):
1380 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001381 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001382 rollback_index_location = info_dict[
1383 "avb_" + partition + "_rollback_index_location"]
1384 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1385
1386
Tianjie20dd8f22020-04-19 15:51:16 -07001387def ConstructAftlMakeImageCommands(output_image):
1388 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001389
1390 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001391 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001392 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1393 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1394 'No AFTL manufacturer key provided.'
1395
1396 vbmeta_image = MakeTempFile()
1397 os.rename(output_image, vbmeta_image)
Tianjiefdda51d2021-05-05 14:46:35 -07001398 build_info = BuildInfo(OPTIONS.info_dict, use_legacy_id=True)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001399 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001400 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001401 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001402 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001403 "--vbmeta_image_path", vbmeta_image,
1404 "--output", output_image,
1405 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001406 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001407 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1408 "--algorithm", "SHA256_RSA4096",
1409 "--padding", "4096"]
1410 if OPTIONS.aftl_signer_helper:
1411 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001412 return aftl_cmd
1413
1414
1415def AddAftlInclusionProof(output_image):
1416 """Appends the aftl inclusion proof to the vbmeta image."""
1417
1418 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001419 RunAndCheckOutput(aftl_cmd)
1420
1421 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1422 output_image, '--transparency_log_pub_keys',
1423 OPTIONS.aftl_key_path]
1424 RunAndCheckOutput(verify_cmd)
1425
1426
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001427def AppendGkiSigningArgs(cmd):
1428 """Append GKI signing arguments for mkbootimg."""
1429 # e.g., --gki_signing_key path/to/signing_key
1430 # --gki_signing_algorithm SHA256_RSA4096"
1431
1432 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1433 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1434 if not key_path:
1435 return
1436
1437 if not os.path.exists(key_path) and OPTIONS.search_path:
1438 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1439 if os.path.exists(new_key_path):
1440 key_path = new_key_path
1441
1442 # Checks key_path exists, before appending --gki_signing_* args.
1443 if not os.path.exists(key_path):
Kelvin Zhang563750f2021-04-28 12:46:17 -04001444 raise ExternalError(
1445 'gki_signing_key_path: "{}" not found'.format(key_path))
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001446
1447 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1448 if key_path and algorithm:
1449 cmd.extend(["--gki_signing_key", key_path,
1450 "--gki_signing_algorithm", algorithm])
1451
1452 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1453 if signature_args:
1454 cmd.extend(["--gki_signing_signature_args", signature_args])
1455
1456
Daniel Norman276f0622019-07-26 14:13:51 -07001457def BuildVBMeta(image_path, partitions, name, needed_partitions):
1458 """Creates a VBMeta image.
1459
1460 It generates the requested VBMeta image. The requested image could be for
1461 top-level or chained VBMeta image, which is determined based on the name.
1462
1463 Args:
1464 image_path: The output path for the new VBMeta image.
1465 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001466 values. Only valid partition names are accepted, as partitions listed
1467 in common.AVB_PARTITIONS and custom partitions listed in
1468 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001469 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1470 needed_partitions: Partitions whose descriptors should be included into the
1471 generated VBMeta image.
1472
1473 Raises:
1474 AssertionError: On invalid input args.
1475 """
1476 avbtool = OPTIONS.info_dict["avb_avbtool"]
1477 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1478 AppendAVBSigningArgs(cmd, name)
1479
Hongguang Chenf23364d2020-04-27 18:36:36 -07001480 custom_partitions = OPTIONS.info_dict.get(
1481 "avb_custom_images_partition_list", "").strip().split()
1482
Daniel Norman276f0622019-07-26 14:13:51 -07001483 for partition, path in partitions.items():
1484 if partition not in needed_partitions:
1485 continue
1486 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001487 partition in AVB_VBMETA_PARTITIONS or
1488 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001489 'Unknown partition: {}'.format(partition)
1490 assert os.path.exists(path), \
1491 'Failed to find {} for {}'.format(path, partition)
1492 cmd.extend(GetAvbPartitionArg(partition, path))
1493
1494 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1495 if args and args.strip():
1496 split_args = shlex.split(args)
1497 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001498 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001499 # as a path relative to source tree, which may not be available at the
1500 # same location when running this script (we have the input target_files
1501 # zip only). For such cases, we additionally scan other locations (e.g.
1502 # IMAGES/, RADIO/, etc) before bailing out.
1503 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001504 chained_image = split_args[index + 1]
1505 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001506 continue
1507 found = False
1508 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1509 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001510 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001511 if os.path.exists(alt_path):
1512 split_args[index + 1] = alt_path
1513 found = True
1514 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001515 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001516 cmd.extend(split_args)
1517
1518 RunAndCheckOutput(cmd)
1519
Tianjie Xueaed60c2020-03-12 00:33:28 -07001520 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001521 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001522 AddAftlInclusionProof(image_path)
1523
Daniel Norman276f0622019-07-26 14:13:51 -07001524
jiajia tang836f76b2021-04-02 14:48:26 +08001525def _MakeRamdisk(sourcedir, fs_config_file=None,
1526 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001527 ramdisk_img = tempfile.NamedTemporaryFile()
1528
1529 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1530 cmd = ["mkbootfs", "-f", fs_config_file,
1531 os.path.join(sourcedir, "RAMDISK")]
1532 else:
1533 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1534 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001535 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001536 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001537 stdout=ramdisk_img.file.fileno())
Luca Stefani31b6bee2020-06-11 13:03:18 +02001538 elif ramdisk_format == RamdiskFormat.XZ:
1539 p2 = Run(["xz", "-f", "-c", "--check=crc32", "--lzma2=dict=32MiB"], stdin=p1.stdout,
1540 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001541 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001542 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001543 else:
1544 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001545
1546 p2.wait()
1547 p1.wait()
1548 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001549 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001550
1551 return ramdisk_img
1552
1553
Steve Muckle9793cf62020-04-08 18:27:00 -07001554def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001555 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001556 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001557
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001558 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001559 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1560 we are building a two-step special image (i.e. building a recovery image to
1561 be loaded into /boot in two-step OTAs).
1562
1563 Return the image data, or None if sourcedir does not appear to contains files
1564 for building the requested image.
1565 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001566
Yifan Hong63c5ca12020-10-08 11:54:02 -07001567 if info_dict is None:
1568 info_dict = OPTIONS.info_dict
1569
Steve Muckle9793cf62020-04-08 18:27:00 -07001570 # "boot" or "recovery", without extension.
1571 partition_name = os.path.basename(sourcedir).lower()
1572
Yifan Hong63c5ca12020-10-08 11:54:02 -07001573 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001574 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001575 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1576 logger.info("Excluded kernel binary from recovery image.")
1577 else:
1578 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001579 else:
1580 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001581 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001582 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001583 return None
1584
1585 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001586 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001587
Doug Zongkereef39442009-04-02 12:14:19 -07001588 img = tempfile.NamedTemporaryFile()
1589
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001590 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001591 ramdisk_format = _GetRamdiskFormat(info_dict)
1592 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1593 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001594
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001595 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1596 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1597
Yifan Hong63c5ca12020-10-08 11:54:02 -07001598 cmd = [mkbootimg]
1599 if kernel:
1600 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001601
Benoit Fradina45a8682014-07-14 21:00:43 +02001602 fn = os.path.join(sourcedir, "second")
1603 if os.access(fn, os.F_OK):
1604 cmd.append("--second")
1605 cmd.append(fn)
1606
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001607 fn = os.path.join(sourcedir, "dtb")
1608 if os.access(fn, os.F_OK):
1609 cmd.append("--dtb")
1610 cmd.append(fn)
1611
Doug Zongker171f1cd2009-06-15 22:36:37 -07001612 fn = os.path.join(sourcedir, "cmdline")
1613 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001614 cmd.append("--cmdline")
1615 cmd.append(open(fn).read().rstrip("\n"))
1616
1617 fn = os.path.join(sourcedir, "base")
1618 if os.access(fn, os.F_OK):
1619 cmd.append("--base")
1620 cmd.append(open(fn).read().rstrip("\n"))
1621
Ying Wang4de6b5b2010-08-25 14:29:34 -07001622 fn = os.path.join(sourcedir, "pagesize")
1623 if os.access(fn, os.F_OK):
1624 cmd.append("--pagesize")
1625 cmd.append(open(fn).read().rstrip("\n"))
1626
Steve Mucklef84668e2020-03-16 19:13:46 -07001627 if partition_name == "recovery":
1628 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301629 if not args:
1630 # Fall back to "mkbootimg_args" for recovery image
1631 # in case "recovery_mkbootimg_args" is not set.
1632 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001633 else:
1634 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001635 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001636 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001637
Tao Bao76def242017-11-21 09:25:31 -08001638 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001639 if args and args.strip():
1640 cmd.extend(shlex.split(args))
1641
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001642 if has_ramdisk:
1643 cmd.extend(["--ramdisk", ramdisk_img.name])
1644
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001645 AppendGkiSigningArgs(cmd)
1646
Tao Baod95e9fd2015-03-29 23:07:41 -07001647 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001648 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001649 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001650 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001651 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001652 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001653
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001654 if partition_name == "recovery":
1655 if info_dict.get("include_recovery_dtbo") == "true":
1656 fn = os.path.join(sourcedir, "recovery_dtbo")
1657 cmd.extend(["--recovery_dtbo", fn])
1658 if info_dict.get("include_recovery_acpio") == "true":
1659 fn = os.path.join(sourcedir, "recovery_acpio")
1660 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001661
Tao Bao986ee862018-10-04 15:46:16 -07001662 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001663
Tao Bao76def242017-11-21 09:25:31 -08001664 if (info_dict.get("boot_signer") == "true" and
Kelvin Zhang563750f2021-04-28 12:46:17 -04001665 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001666 # Hard-code the path as "/boot" for two-step special recovery image (which
1667 # will be loaded into /boot during the two-step OTA).
1668 if two_step_image:
1669 path = "/boot"
1670 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001671 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001672 cmd = [OPTIONS.boot_signer_path]
1673 cmd.extend(OPTIONS.boot_signer_args)
1674 cmd.extend([path, img.name,
1675 info_dict["verity_key"] + ".pk8",
1676 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001677 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001678
Tao Baod95e9fd2015-03-29 23:07:41 -07001679 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001680 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001681 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001682 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001683 # We have switched from the prebuilt futility binary to using the tool
1684 # (futility-host) built from the source. Override the setting in the old
1685 # TF.zip.
1686 futility = info_dict["futility"]
1687 if futility.startswith("prebuilts/"):
1688 futility = "futility-host"
1689 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001690 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001691 info_dict["vboot_key"] + ".vbprivk",
1692 info_dict["vboot_subkey"] + ".vbprivk",
1693 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001694 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001695 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001696
Tao Baof3282b42015-04-01 11:21:55 -07001697 # Clean up the temp files.
1698 img_unsigned.close()
1699 img_keyblock.close()
1700
David Zeuthen8fecb282017-12-01 16:24:01 -05001701 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001702 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001703 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001704 if partition_name == "recovery":
1705 part_size = info_dict["recovery_size"]
1706 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001707 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001708 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001709 "--partition_size", str(part_size), "--partition_name",
1710 partition_name]
1711 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001712 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001713 if args and args.strip():
1714 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001715 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001716
1717 img.seek(os.SEEK_SET, 0)
1718 data = img.read()
1719
1720 if has_ramdisk:
1721 ramdisk_img.close()
1722 img.close()
1723
1724 return data
1725
1726
Bowgo Tsaib23656d2021-05-20 00:14:42 +08001727def _SignBootableImage(image_path, prebuilt_name, partition_name,
1728 info_dict=None):
1729 """Performs AVB signing for a prebuilt boot.img.
1730
1731 Args:
1732 image_path: The full path of the image, e.g., /path/to/boot.img.
1733 prebuilt_name: The prebuilt image name, e.g., boot.img, boot-5.4-gz.img,
1734 boot-5.10.img, recovery.img.
1735 partition_name: The partition name, e.g., 'boot' or 'recovery'.
1736 info_dict: The information dict read from misc_info.txt.
1737 """
1738 if info_dict is None:
1739 info_dict = OPTIONS.info_dict
1740
1741 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
1742 if info_dict.get("avb_enable") == "true":
1743 avbtool = info_dict["avb_avbtool"]
1744 if partition_name == "recovery":
1745 part_size = info_dict["recovery_size"]
1746 else:
1747 part_size = info_dict[prebuilt_name.replace(".img", "_size")]
1748
1749 cmd = [avbtool, "add_hash_footer", "--image", image_path,
1750 "--partition_size", str(part_size), "--partition_name",
1751 partition_name]
1752 AppendAVBSigningArgs(cmd, partition_name)
1753 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
1754 if args and args.strip():
1755 cmd.extend(shlex.split(args))
1756 RunAndCheckOutput(cmd)
1757
1758
Doug Zongkerd5131602012-08-02 14:46:42 -07001759def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001760 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001761 """Return a File object with the desired bootable image.
1762
1763 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1764 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1765 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001766
Bowgo Tsaib23656d2021-05-20 00:14:42 +08001767 if info_dict is None:
1768 info_dict = OPTIONS.info_dict
1769
Doug Zongker55d93282011-01-25 17:03:34 -08001770 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1771 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001772 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001773 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001774
1775 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1776 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001777 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001778 return File.FromLocalFile(name, prebuilt_path)
1779
Bowgo Tsaib23656d2021-05-20 00:14:42 +08001780 prebuilt_path = os.path.join(unpack_dir, "PREBUILT_IMAGES", prebuilt_name)
1781 if os.path.exists(prebuilt_path):
1782 logger.info("Re-signing prebuilt %s from PREBUILT_IMAGES...", prebuilt_name)
1783 signed_img = MakeTempFile()
1784 shutil.copy(prebuilt_path, signed_img)
1785 partition_name = tree_subdir.lower()
1786 _SignBootableImage(signed_img, prebuilt_name, partition_name, info_dict)
1787 return File.FromLocalFile(name, signed_img)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001788
Bowgo Tsaib23656d2021-05-20 00:14:42 +08001789 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001790
1791 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001792 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1793 # for recovery.
1794 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1795 prebuilt_name != "boot.img" or
1796 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001797
Doug Zongker6f1d0312014-08-22 08:07:12 -07001798 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001799 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001800 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001801 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001802 if data:
1803 return File(name, data)
1804 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001805
Doug Zongkereef39442009-04-02 12:14:19 -07001806
Steve Mucklee1b10862019-07-10 10:49:37 -07001807def _BuildVendorBootImage(sourcedir, info_dict=None):
1808 """Build a vendor boot image from the specified sourcedir.
1809
1810 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1811 turn them into a vendor boot image.
1812
1813 Return the image data, or None if sourcedir does not appear to contains files
1814 for building the requested image.
1815 """
1816
1817 if info_dict is None:
1818 info_dict = OPTIONS.info_dict
1819
1820 img = tempfile.NamedTemporaryFile()
1821
jiajia tang836f76b2021-04-02 14:48:26 +08001822 ramdisk_format = _GetRamdiskFormat(info_dict)
1823 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001824
1825 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1826 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1827
1828 cmd = [mkbootimg]
1829
1830 fn = os.path.join(sourcedir, "dtb")
1831 if os.access(fn, os.F_OK):
1832 cmd.append("--dtb")
1833 cmd.append(fn)
1834
1835 fn = os.path.join(sourcedir, "vendor_cmdline")
1836 if os.access(fn, os.F_OK):
1837 cmd.append("--vendor_cmdline")
1838 cmd.append(open(fn).read().rstrip("\n"))
1839
1840 fn = os.path.join(sourcedir, "base")
1841 if os.access(fn, os.F_OK):
1842 cmd.append("--base")
1843 cmd.append(open(fn).read().rstrip("\n"))
1844
1845 fn = os.path.join(sourcedir, "pagesize")
1846 if os.access(fn, os.F_OK):
1847 cmd.append("--pagesize")
1848 cmd.append(open(fn).read().rstrip("\n"))
1849
1850 args = info_dict.get("mkbootimg_args")
1851 if args and args.strip():
1852 cmd.extend(shlex.split(args))
1853
1854 args = info_dict.get("mkbootimg_version_args")
1855 if args and args.strip():
1856 cmd.extend(shlex.split(args))
1857
1858 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1859 cmd.extend(["--vendor_boot", img.name])
1860
Devin Moore50509012021-01-13 10:45:04 -08001861 fn = os.path.join(sourcedir, "vendor_bootconfig")
1862 if os.access(fn, os.F_OK):
1863 cmd.append("--vendor_bootconfig")
1864 cmd.append(fn)
1865
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001866 ramdisk_fragment_imgs = []
1867 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1868 if os.access(fn, os.F_OK):
1869 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1870 for ramdisk_fragment in ramdisk_fragments:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001871 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1872 ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001873 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Kelvin Zhang563750f2021-04-28 12:46:17 -04001874 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS",
1875 ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001876 # Use prebuilt image if found, else create ramdisk from supplied files.
1877 if os.access(fn, os.F_OK):
1878 ramdisk_fragment_pathname = fn
1879 else:
Kelvin Zhang563750f2021-04-28 12:46:17 -04001880 ramdisk_fragment_root = os.path.join(
1881 sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001882 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1883 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001884 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1885 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1886 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1887
Steve Mucklee1b10862019-07-10 10:49:37 -07001888 RunAndCheckOutput(cmd)
1889
1890 # AVB: if enabled, calculate and add hash.
1891 if info_dict.get("avb_enable") == "true":
1892 avbtool = info_dict["avb_avbtool"]
1893 part_size = info_dict["vendor_boot_size"]
1894 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001895 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001896 AppendAVBSigningArgs(cmd, "vendor_boot")
1897 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1898 if args and args.strip():
1899 cmd.extend(shlex.split(args))
1900 RunAndCheckOutput(cmd)
1901
1902 img.seek(os.SEEK_SET, 0)
1903 data = img.read()
1904
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001905 for f in ramdisk_fragment_imgs:
1906 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001907 ramdisk_img.close()
1908 img.close()
1909
1910 return data
1911
1912
1913def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1914 info_dict=None):
1915 """Return a File object with the desired vendor boot image.
1916
1917 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1918 the source files in 'unpack_dir'/'tree_subdir'."""
1919
1920 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1921 if os.path.exists(prebuilt_path):
1922 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1923 return File.FromLocalFile(name, prebuilt_path)
1924
1925 logger.info("building image from target_files %s...", tree_subdir)
1926
1927 if info_dict is None:
1928 info_dict = OPTIONS.info_dict
1929
Kelvin Zhang0876c412020-06-23 15:06:58 -04001930 data = _BuildVendorBootImage(
1931 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001932 if data:
1933 return File(name, data)
1934 return None
1935
1936
Narayan Kamatha07bf042017-08-14 14:49:21 +01001937def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001938 """Gunzips the given gzip compressed file to a given output file."""
1939 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001940 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001941 shutil.copyfileobj(in_file, out_file)
1942
1943
Tao Bao0ff15de2019-03-20 11:26:06 -07001944def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001945 """Unzips the archive to the given directory.
1946
1947 Args:
1948 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001949 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001950 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1951 archvie. Non-matching patterns will be filtered out. If there's no match
1952 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001953 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001954 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001955 if patterns is not None:
1956 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001957 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001958 names = input_zip.namelist()
1959 filtered = [
1960 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1961
1962 # There isn't any matching files. Don't unzip anything.
1963 if not filtered:
1964 return
1965 cmd.extend(filtered)
1966
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001967 RunAndCheckOutput(cmd)
1968
1969
Doug Zongker75f17362009-12-08 13:46:44 -08001970def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001971 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001972
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001973 Args:
1974 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1975 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1976
1977 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1978 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001979
Tao Bao1c830bf2017-12-25 10:43:47 -08001980 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001981 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001982 """
Doug Zongkereef39442009-04-02 12:14:19 -07001983
Tao Bao1c830bf2017-12-25 10:43:47 -08001984 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001985 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1986 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001987 UnzipToDir(m.group(1), tmp, pattern)
1988 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001989 filename = m.group(1)
1990 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001991 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001992
Tao Baodba59ee2018-01-09 13:21:02 -08001993 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001994
1995
Yifan Hong8a66a712019-04-04 15:37:57 -07001996def GetUserImage(which, tmpdir, input_zip,
1997 info_dict=None,
1998 allow_shared_blocks=None,
1999 hashtree_info_generator=None,
2000 reset_file_map=False):
2001 """Returns an Image object suitable for passing to BlockImageDiff.
2002
2003 This function loads the specified image from the given path. If the specified
2004 image is sparse, it also performs additional processing for OTA purpose. For
2005 example, it always adds block 0 to clobbered blocks list. It also detects
2006 files that cannot be reconstructed from the block list, for whom we should
2007 avoid applying imgdiff.
2008
2009 Args:
2010 which: The partition name.
2011 tmpdir: The directory that contains the prebuilt image and block map file.
2012 input_zip: The target-files ZIP archive.
2013 info_dict: The dict to be looked up for relevant info.
2014 allow_shared_blocks: If image is sparse, whether having shared blocks is
2015 allowed. If none, it is looked up from info_dict.
2016 hashtree_info_generator: If present and image is sparse, generates the
2017 hashtree_info for this sparse image.
2018 reset_file_map: If true and image is sparse, reset file map before returning
2019 the image.
2020 Returns:
2021 A Image object. If it is a sparse image and reset_file_map is False, the
2022 image will have file_map info loaded.
2023 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002024 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07002025 info_dict = LoadInfoDict(input_zip)
2026
2027 is_sparse = info_dict.get("extfs_sparse_flag")
2028
2029 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
2030 # shared blocks (i.e. some blocks will show up in multiple files' block
2031 # list). We can only allocate such shared blocks to the first "owner", and
2032 # disable imgdiff for all later occurrences.
2033 if allow_shared_blocks is None:
2034 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
2035
2036 if is_sparse:
2037 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2038 hashtree_info_generator)
2039 if reset_file_map:
2040 img.ResetFileMap()
2041 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04002042 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07002043
2044
2045def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
2046 """Returns a Image object suitable for passing to BlockImageDiff.
2047
2048 This function loads the specified non-sparse image from the given path.
2049
2050 Args:
2051 which: The partition name.
2052 tmpdir: The directory that contains the prebuilt image and block map file.
2053 Returns:
2054 A Image object.
2055 """
2056 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2057 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2058
2059 # The image and map files must have been created prior to calling
2060 # ota_from_target_files.py (since LMP).
2061 assert os.path.exists(path) and os.path.exists(mappath)
2062
Tianjie Xu41976c72019-07-03 13:57:01 -07002063 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
2064
Yifan Hong8a66a712019-04-04 15:37:57 -07002065
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002066def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
2067 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08002068 """Returns a SparseImage object suitable for passing to BlockImageDiff.
2069
2070 This function loads the specified sparse image from the given path, and
2071 performs additional processing for OTA purpose. For example, it always adds
2072 block 0 to clobbered blocks list. It also detects files that cannot be
2073 reconstructed from the block list, for whom we should avoid applying imgdiff.
2074
2075 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07002076 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08002077 tmpdir: The directory that contains the prebuilt image and block map file.
2078 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08002079 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002080 hashtree_info_generator: If present, generates the hashtree_info for this
2081 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08002082 Returns:
2083 A SparseImage object, with file_map info loaded.
2084 """
Tao Baoc765cca2018-01-31 17:32:40 -08002085 path = os.path.join(tmpdir, "IMAGES", which + ".img")
2086 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
2087
2088 # The image and map files must have been created prior to calling
2089 # ota_from_target_files.py (since LMP).
2090 assert os.path.exists(path) and os.path.exists(mappath)
2091
2092 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
2093 # it to clobbered_blocks so that it will be written to the target
2094 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
2095 clobbered_blocks = "0"
2096
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07002097 image = sparse_img.SparseImage(
2098 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
2099 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08002100
2101 # block.map may contain less blocks, because mke2fs may skip allocating blocks
2102 # if they contain all zeros. We can't reconstruct such a file from its block
2103 # list. Tag such entries accordingly. (Bug: 65213616)
2104 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08002105 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07002106 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08002107 continue
2108
Tom Cherryd14b8952018-08-09 14:26:00 -07002109 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
2110 # filename listed in system.map may contain an additional leading slash
2111 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
2112 # results.
wangshumin71af07a2021-02-24 11:08:47 +08002113 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07002114 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08002115 arcname = entry.lstrip('/')
2116 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07002117 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002118 else:
2119 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002120
2121 assert arcname in input_zip.namelist(), \
2122 "Failed to find the ZIP entry for {}".format(entry)
2123
Tao Baoc765cca2018-01-31 17:32:40 -08002124 info = input_zip.getinfo(arcname)
2125 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002126
2127 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002128 # image, check the original block list to determine its completeness. Note
2129 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002130 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002131 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002132
Tao Baoc765cca2018-01-31 17:32:40 -08002133 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2134 ranges.extra['incomplete'] = True
2135
2136 return image
2137
2138
Doug Zongkereef39442009-04-02 12:14:19 -07002139def GetKeyPasswords(keylist):
2140 """Given a list of keys, prompt the user to enter passwords for
2141 those which require them. Return a {key: password} dict. password
2142 will be None if the key has no password."""
2143
Doug Zongker8ce7c252009-05-22 13:34:54 -07002144 no_passwords = []
2145 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002146 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002147 devnull = open("/dev/null", "w+b")
2148 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002149 # We don't need a password for things that aren't really keys.
2150 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002151 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002152 continue
2153
T.R. Fullhart37e10522013-03-18 10:31:26 -07002154 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002155 "-inform", "DER", "-nocrypt"],
2156 stdin=devnull.fileno(),
2157 stdout=devnull.fileno(),
2158 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002159 p.communicate()
2160 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002161 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002162 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002163 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002164 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2165 "-inform", "DER", "-passin", "pass:"],
2166 stdin=devnull.fileno(),
2167 stdout=devnull.fileno(),
2168 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002169 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002170 if p.returncode == 0:
2171 # Encrypted key with empty string as password.
2172 key_passwords[k] = ''
2173 elif stderr.startswith('Error decrypting key'):
2174 # Definitely encrypted key.
2175 # It would have said "Error reading key" if it didn't parse correctly.
2176 need_passwords.append(k)
2177 else:
2178 # Potentially, a type of key that openssl doesn't understand.
2179 # We'll let the routines in signapk.jar handle it.
2180 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002181 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002182
T.R. Fullhart37e10522013-03-18 10:31:26 -07002183 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002184 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002185 return key_passwords
2186
2187
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002188def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002189 """Gets the minSdkVersion declared in the APK.
2190
changho.shin0f125362019-07-08 10:59:00 +09002191 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002192 This can be both a decimal number (API Level) or a codename.
2193
2194 Args:
2195 apk_name: The APK filename.
2196
2197 Returns:
2198 The parsed SDK version string.
2199
2200 Raises:
2201 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002202 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002203 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002204 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002205 stderr=subprocess.PIPE)
2206 stdoutdata, stderrdata = proc.communicate()
2207 if proc.returncode != 0:
2208 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002209 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002210 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002211
Tao Baof47bf0f2018-03-21 23:28:51 -07002212 for line in stdoutdata.split("\n"):
2213 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002214 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2215 if m:
2216 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002217 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002218
2219
2220def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002221 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002222
Tao Baof47bf0f2018-03-21 23:28:51 -07002223 If minSdkVersion is set to a codename, it is translated to a number using the
2224 provided map.
2225
2226 Args:
2227 apk_name: The APK filename.
2228
2229 Returns:
2230 The parsed SDK version number.
2231
2232 Raises:
2233 ExternalError: On failing to get the min SDK version number.
2234 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002235 version = GetMinSdkVersion(apk_name)
2236 try:
2237 return int(version)
2238 except ValueError:
2239 # Not a decimal number. Codename?
2240 if version in codename_to_api_level_map:
2241 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002242 raise ExternalError(
2243 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2244 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002245
2246
2247def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002248 codename_to_api_level_map=None, whole_file=False,
2249 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002250 """Sign the input_name zip/jar/apk, producing output_name. Use the
2251 given key and password (the latter may be None if the key does not
2252 have a password.
2253
Doug Zongker951495f2009-08-14 12:44:19 -07002254 If whole_file is true, use the "-w" option to SignApk to embed a
2255 signature that covers the whole file in the archive comment of the
2256 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002257
2258 min_api_level is the API Level (int) of the oldest platform this file may end
2259 up on. If not specified for an APK, the API Level is obtained by interpreting
2260 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2261
2262 codename_to_api_level_map is needed to translate the codename which may be
2263 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002264
2265 Caller may optionally specify extra args to be passed to SignApk, which
2266 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002267 """
Tao Bao76def242017-11-21 09:25:31 -08002268 if codename_to_api_level_map is None:
2269 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002270 if extra_signapk_args is None:
2271 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002272
Alex Klyubin9667b182015-12-10 13:38:50 -08002273 java_library_path = os.path.join(
2274 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2275
Tao Baoe95540e2016-11-08 12:08:53 -08002276 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2277 ["-Djava.library.path=" + java_library_path,
2278 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002279 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002280 if whole_file:
2281 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002282
2283 min_sdk_version = min_api_level
2284 if min_sdk_version is None:
2285 if not whole_file:
2286 min_sdk_version = GetMinSdkVersionInt(
2287 input_name, codename_to_api_level_map)
2288 if min_sdk_version is not None:
2289 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2290
T.R. Fullhart37e10522013-03-18 10:31:26 -07002291 cmd.extend([key + OPTIONS.public_key_suffix,
2292 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002293 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002294
Tao Bao73dd4f42018-10-04 16:25:33 -07002295 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002296 if password is not None:
2297 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002298 stdoutdata, _ = proc.communicate(password)
2299 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002300 raise ExternalError(
2301 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002302 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002303
Doug Zongkereef39442009-04-02 12:14:19 -07002304
Doug Zongker37974732010-09-16 17:44:38 -07002305def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002306 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002307
Tao Bao9dd909e2017-11-14 11:27:32 -08002308 For non-AVB images, raise exception if the data is too big. Print a warning
2309 if the data is nearing the maximum size.
2310
2311 For AVB images, the actual image size should be identical to the limit.
2312
2313 Args:
2314 data: A string that contains all the data for the partition.
2315 target: The partition name. The ".img" suffix is optional.
2316 info_dict: The dict to be looked up for relevant info.
2317 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002318 if target.endswith(".img"):
2319 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002320 mount_point = "/" + target
2321
Ying Wangf8824af2014-06-03 14:07:27 -07002322 fs_type = None
2323 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002324 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002325 if mount_point == "/userdata":
2326 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002327 p = info_dict["fstab"][mount_point]
2328 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002329 device = p.device
2330 if "/" in device:
2331 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002332 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002333 if not fs_type or not limit:
2334 return
Doug Zongkereef39442009-04-02 12:14:19 -07002335
Andrew Boie0f9aec82012-02-14 09:32:52 -08002336 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002337 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2338 # path.
2339 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2340 if size != limit:
2341 raise ExternalError(
2342 "Mismatching image size for %s: expected %d actual %d" % (
2343 target, limit, size))
2344 else:
2345 pct = float(size) * 100.0 / limit
2346 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2347 if pct >= 99.0:
2348 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002349
2350 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002351 logger.warning("\n WARNING: %s\n", msg)
2352 else:
2353 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002354
2355
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002356def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002357 """Parses the APK certs info from a given target-files zip.
2358
2359 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2360 tuple with the following elements: (1) a dictionary that maps packages to
2361 certs (based on the "certificate" and "private_key" attributes in the file;
2362 (2) a string representing the extension of compressed APKs in the target files
2363 (e.g ".gz", ".bro").
2364
2365 Args:
2366 tf_zip: The input target_files ZipFile (already open).
2367
2368 Returns:
2369 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2370 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2371 no compressed APKs.
2372 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002373 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002374 compressed_extension = None
2375
Tao Bao0f990332017-09-08 19:02:54 -07002376 # META/apkcerts.txt contains the info for _all_ the packages known at build
2377 # time. Filter out the ones that are not installed.
2378 installed_files = set()
2379 for name in tf_zip.namelist():
2380 basename = os.path.basename(name)
2381 if basename:
2382 installed_files.add(basename)
2383
Tao Baoda30cfa2017-12-01 16:19:46 -08002384 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002385 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002386 if not line:
2387 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002388 m = re.match(
2389 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002390 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2391 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002392 line)
2393 if not m:
2394 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002395
Tao Bao818ddf52018-01-05 11:17:34 -08002396 matches = m.groupdict()
2397 cert = matches["CERT"]
2398 privkey = matches["PRIVKEY"]
2399 name = matches["NAME"]
2400 this_compressed_extension = matches["COMPRESSED"]
2401
2402 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2403 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2404 if cert in SPECIAL_CERT_STRINGS and not privkey:
2405 certmap[name] = cert
2406 elif (cert.endswith(OPTIONS.public_key_suffix) and
2407 privkey.endswith(OPTIONS.private_key_suffix) and
2408 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2409 certmap[name] = cert[:-public_key_suffix_len]
2410 else:
2411 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2412
2413 if not this_compressed_extension:
2414 continue
2415
2416 # Only count the installed files.
2417 filename = name + '.' + this_compressed_extension
2418 if filename not in installed_files:
2419 continue
2420
2421 # Make sure that all the values in the compression map have the same
2422 # extension. We don't support multiple compression methods in the same
2423 # system image.
2424 if compressed_extension:
2425 if this_compressed_extension != compressed_extension:
2426 raise ValueError(
2427 "Multiple compressed extensions: {} vs {}".format(
2428 compressed_extension, this_compressed_extension))
2429 else:
2430 compressed_extension = this_compressed_extension
2431
2432 return (certmap,
2433 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002434
2435
Doug Zongkereef39442009-04-02 12:14:19 -07002436COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002437Global options
2438
2439 -p (--path) <dir>
2440 Prepend <dir>/bin to the list of places to search for binaries run by this
2441 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002442
Doug Zongker05d3dea2009-06-22 11:32:31 -07002443 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002444 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002445
Tao Bao30df8b42018-04-23 15:32:53 -07002446 -x (--extra) <key=value>
2447 Add a key/value pair to the 'extras' dict, which device-specific extension
2448 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002449
Doug Zongkereef39442009-04-02 12:14:19 -07002450 -v (--verbose)
2451 Show command lines being executed.
2452
2453 -h (--help)
2454 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002455
2456 --logfile <file>
2457 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002458"""
2459
Kelvin Zhang0876c412020-06-23 15:06:58 -04002460
Doug Zongkereef39442009-04-02 12:14:19 -07002461def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002462 print(docstring.rstrip("\n"))
2463 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002464
2465
2466def ParseOptions(argv,
2467 docstring,
2468 extra_opts="", extra_long_opts=(),
2469 extra_option_handler=None):
2470 """Parse the options in argv and return any arguments that aren't
2471 flags. docstring is the calling module's docstring, to be displayed
2472 for errors and -h. extra_opts and extra_long_opts are for flags
2473 defined by the caller, which are processed by passing them to
2474 extra_option_handler."""
2475
2476 try:
2477 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002478 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002479 ["help", "verbose", "path=", "signapk_path=",
2480 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002481 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002482 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2483 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002484 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2485 "aftl_key_path=", "aftl_manufacturer_key_path=",
2486 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002487 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002488 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002489 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002490 sys.exit(2)
2491
Doug Zongkereef39442009-04-02 12:14:19 -07002492 for o, a in opts:
2493 if o in ("-h", "--help"):
2494 Usage(docstring)
2495 sys.exit()
2496 elif o in ("-v", "--verbose"):
2497 OPTIONS.verbose = True
2498 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002499 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002500 elif o in ("--signapk_path",):
2501 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002502 elif o in ("--signapk_shared_library_path",):
2503 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002504 elif o in ("--extra_signapk_args",):
2505 OPTIONS.extra_signapk_args = shlex.split(a)
2506 elif o in ("--java_path",):
2507 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002508 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002509 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002510 elif o in ("--android_jar_path",):
2511 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002512 elif o in ("--public_key_suffix",):
2513 OPTIONS.public_key_suffix = a
2514 elif o in ("--private_key_suffix",):
2515 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002516 elif o in ("--boot_signer_path",):
2517 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002518 elif o in ("--boot_signer_args",):
2519 OPTIONS.boot_signer_args = shlex.split(a)
2520 elif o in ("--verity_signer_path",):
2521 OPTIONS.verity_signer_path = a
2522 elif o in ("--verity_signer_args",):
2523 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002524 elif o in ("--aftl_tool_path",):
2525 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002526 elif o in ("--aftl_server",):
2527 OPTIONS.aftl_server = a
2528 elif o in ("--aftl_key_path",):
2529 OPTIONS.aftl_key_path = a
2530 elif o in ("--aftl_manufacturer_key_path",):
2531 OPTIONS.aftl_manufacturer_key_path = a
2532 elif o in ("--aftl_signer_helper",):
2533 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002534 elif o in ("-s", "--device_specific"):
2535 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002536 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002537 key, value = a.split("=", 1)
2538 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002539 elif o in ("--logfile",):
2540 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002541 else:
2542 if extra_option_handler is None or not extra_option_handler(o, a):
2543 assert False, "unknown option \"%s\"" % (o,)
2544
Doug Zongker85448772014-09-09 14:59:20 -07002545 if OPTIONS.search_path:
2546 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2547 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002548
2549 return args
2550
2551
Tao Bao4c851b12016-09-19 13:54:38 -07002552def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002553 """Make a temp file and add it to the list of things to be deleted
2554 when Cleanup() is called. Return the filename."""
2555 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2556 os.close(fd)
2557 OPTIONS.tempfiles.append(fn)
2558 return fn
2559
2560
Tao Bao1c830bf2017-12-25 10:43:47 -08002561def MakeTempDir(prefix='tmp', suffix=''):
2562 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2563
2564 Returns:
2565 The absolute pathname of the new directory.
2566 """
2567 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2568 OPTIONS.tempfiles.append(dir_name)
2569 return dir_name
2570
2571
Doug Zongkereef39442009-04-02 12:14:19 -07002572def Cleanup():
2573 for i in OPTIONS.tempfiles:
2574 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002575 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002576 else:
2577 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002578 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002579
2580
2581class PasswordManager(object):
2582 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002583 self.editor = os.getenv("EDITOR")
2584 self.pwfile = os.getenv("ANDROID_PW_FILE")
Tom Powell7ff79132017-01-20 20:47:49 -08002585 self.secure_storage_cmd = os.getenv("ANDROID_SECURE_STORAGE_CMD", None)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002586
2587 def GetPasswords(self, items):
2588 """Get passwords corresponding to each string in 'items',
2589 returning a dict. (The dict may have keys in addition to the
2590 values in 'items'.)
2591
2592 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2593 user edit that file to add more needed passwords. If no editor is
2594 available, or $ANDROID_PW_FILE isn't define, prompts the user
2595 interactively in the ordinary way.
2596 """
2597
2598 current = self.ReadFile()
2599
2600 first = True
2601 while True:
2602 missing = []
2603 for i in items:
2604 if i not in current or not current[i]:
Tom Powell7ff79132017-01-20 20:47:49 -08002605 # Attempt to load using ANDROID_SECURE_STORAGE_CMD
2606 if self.secure_storage_cmd:
2607 try:
2608 os.environ["TMP__KEY_FILE_NAME"] = str(i)
2609 ps = subprocess.Popen(self.secure_storage_cmd, shell=True, stdout=subprocess.PIPE)
2610 output = ps.communicate()[0]
2611 if ps.returncode == 0:
2612 current[i] = output
2613 except Exception as e:
2614 print(e)
2615 pass
2616 if i not in current or not current[i]:
2617 missing.append(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002618 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002619 if not missing:
Tom Powell7ff79132017-01-20 20:47:49 -08002620 if "ANDROID_SECURE_STORAGE_CMD" in os.environ:
2621 del os.environ["ANDROID_SECURE_STORAGE_CMD"]
Dan Albert8b72aef2015-03-23 19:13:21 -07002622 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002623
2624 for i in missing:
2625 current[i] = ""
2626
2627 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002628 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002629 if sys.version_info[0] >= 3:
2630 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002631 answer = raw_input("try to edit again? [y]> ").strip()
2632 if answer and answer[0] not in 'yY':
2633 raise RuntimeError("key passwords unavailable")
2634 first = False
2635
2636 current = self.UpdateAndReadFile(current)
2637
Kelvin Zhang0876c412020-06-23 15:06:58 -04002638 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002639 """Prompt the user to enter a value (password) for each key in
2640 'current' whose value is fales. Returns a new dict with all the
2641 values.
2642 """
2643 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002644 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002645 if v:
2646 result[k] = v
2647 else:
2648 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002649 result[k] = getpass.getpass(
2650 "Enter password for %s key> " % k).strip()
2651 if result[k]:
2652 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002653 return result
2654
2655 def UpdateAndReadFile(self, current):
2656 if not self.editor or not self.pwfile:
2657 return self.PromptResult(current)
2658
2659 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002660 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002661 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2662 f.write("# (Additional spaces are harmless.)\n\n")
2663
2664 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002665 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002666 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002667 f.write("[[[ %s ]]] %s\n" % (v, k))
2668 if not v and first_line is None:
2669 # position cursor on first line with no password.
2670 first_line = i + 4
2671 f.close()
2672
Tao Bao986ee862018-10-04 15:46:16 -07002673 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002674
2675 return self.ReadFile()
2676
2677 def ReadFile(self):
2678 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002679 if self.pwfile is None:
2680 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002681 try:
2682 f = open(self.pwfile, "r")
2683 for line in f:
2684 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002685 if not line or line[0] == '#':
2686 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002687 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2688 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002689 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002690 else:
2691 result[m.group(2)] = m.group(1)
2692 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002693 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002694 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002695 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002696 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002697
2698
Dan Albert8e0178d2015-01-27 15:53:15 -08002699def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2700 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002701
2702 # http://b/18015246
2703 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2704 # for files larger than 2GiB. We can work around this by adjusting their
2705 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2706 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2707 # it isn't clear to me exactly what circumstances cause this).
2708 # `zipfile.write()` must be used directly to work around this.
2709 #
2710 # This mess can be avoided if we port to python3.
2711 saved_zip64_limit = zipfile.ZIP64_LIMIT
2712 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2713
2714 if compress_type is None:
2715 compress_type = zip_file.compression
2716 if arcname is None:
2717 arcname = filename
2718
2719 saved_stat = os.stat(filename)
2720
2721 try:
2722 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2723 # file to be zipped and reset it when we're done.
2724 os.chmod(filename, perms)
2725
2726 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002727 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2728 # intentional. zip stores datetimes in local time without a time zone
2729 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2730 # in the zip archive.
2731 local_epoch = datetime.datetime.fromtimestamp(0)
2732 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002733 os.utime(filename, (timestamp, timestamp))
2734
2735 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2736 finally:
2737 os.chmod(filename, saved_stat.st_mode)
2738 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2739 zipfile.ZIP64_LIMIT = saved_zip64_limit
2740
2741
Tao Bao58c1b962015-05-20 09:32:18 -07002742def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002743 compress_type=None):
2744 """Wrap zipfile.writestr() function to work around the zip64 limit.
2745
2746 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2747 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2748 when calling crc32(bytes).
2749
2750 But it still works fine to write a shorter string into a large zip file.
2751 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2752 when we know the string won't be too long.
2753 """
2754
2755 saved_zip64_limit = zipfile.ZIP64_LIMIT
2756 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2757
2758 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2759 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002760 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002761 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002762 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002763 else:
Tao Baof3282b42015-04-01 11:21:55 -07002764 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002765 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2766 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2767 # such a case (since
2768 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2769 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2770 # permission bits. We follow the logic in Python 3 to get consistent
2771 # behavior between using the two versions.
2772 if not zinfo.external_attr:
2773 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002774
2775 # If compress_type is given, it overrides the value in zinfo.
2776 if compress_type is not None:
2777 zinfo.compress_type = compress_type
2778
Tao Bao58c1b962015-05-20 09:32:18 -07002779 # If perms is given, it has a priority.
2780 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002781 # If perms doesn't set the file type, mark it as a regular file.
2782 if perms & 0o770000 == 0:
2783 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002784 zinfo.external_attr = perms << 16
2785
Tao Baof3282b42015-04-01 11:21:55 -07002786 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002787 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2788
Dan Albert8b72aef2015-03-23 19:13:21 -07002789 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002790 zipfile.ZIP64_LIMIT = saved_zip64_limit
2791
2792
Tao Bao89d7ab22017-12-14 17:05:33 -08002793def ZipDelete(zip_filename, entries):
2794 """Deletes entries from a ZIP file.
2795
2796 Since deleting entries from a ZIP file is not supported, it shells out to
2797 'zip -d'.
2798
2799 Args:
2800 zip_filename: The name of the ZIP file.
2801 entries: The name of the entry, or the list of names to be deleted.
2802
2803 Raises:
2804 AssertionError: In case of non-zero return from 'zip'.
2805 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002806 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002807 entries = [entries]
2808 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002809 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002810
2811
Tao Baof3282b42015-04-01 11:21:55 -07002812def ZipClose(zip_file):
2813 # http://b/18015246
2814 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2815 # central directory.
2816 saved_zip64_limit = zipfile.ZIP64_LIMIT
2817 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2818
2819 zip_file.close()
2820
2821 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002822
2823
2824class DeviceSpecificParams(object):
2825 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002826
Doug Zongker05d3dea2009-06-22 11:32:31 -07002827 def __init__(self, **kwargs):
2828 """Keyword arguments to the constructor become attributes of this
2829 object, which is passed to all functions in the device-specific
2830 module."""
Tao Bao38884282019-07-10 22:20:56 -07002831 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002832 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002833 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002834
2835 if self.module is None:
2836 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002837 if not path:
2838 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002839 try:
2840 if os.path.isdir(path):
2841 info = imp.find_module("releasetools", [path])
2842 else:
2843 d, f = os.path.split(path)
2844 b, x = os.path.splitext(f)
2845 if x == ".py":
2846 f = b
2847 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002848 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002849 self.module = imp.load_module("device_specific", *info)
2850 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002851 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002852
2853 def _DoCall(self, function_name, *args, **kwargs):
2854 """Call the named function in the device-specific module, passing
2855 the given args and kwargs. The first argument to the call will be
2856 the DeviceSpecific object itself. If there is no module, or the
2857 module does not define the function, return the value of the
2858 'default' kwarg (which itself defaults to None)."""
2859 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002860 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002861 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2862
2863 def FullOTA_Assertions(self):
2864 """Called after emitting the block of assertions at the top of a
2865 full OTA package. Implementations can add whatever additional
2866 assertions they like."""
2867 return self._DoCall("FullOTA_Assertions")
2868
Doug Zongkere5ff5902012-01-17 10:55:37 -08002869 def FullOTA_InstallBegin(self):
2870 """Called at the start of full OTA installation."""
2871 return self._DoCall("FullOTA_InstallBegin")
2872
Yifan Hong10c530d2018-12-27 17:34:18 -08002873 def FullOTA_GetBlockDifferences(self):
2874 """Called during full OTA installation and verification.
2875 Implementation should return a list of BlockDifference objects describing
2876 the update on each additional partitions.
2877 """
2878 return self._DoCall("FullOTA_GetBlockDifferences")
2879
Doug Zongker05d3dea2009-06-22 11:32:31 -07002880 def FullOTA_InstallEnd(self):
2881 """Called at the end of full OTA installation; typically this is
2882 used to install the image for the device's baseband processor."""
2883 return self._DoCall("FullOTA_InstallEnd")
2884
M1chab7cf28a2014-11-25 15:30:48 +01002885 def FullOTA_PostValidate(self):
2886 """Called after installing and validating /system; typically this is
2887 used to resize the system partition after a block based installation."""
2888 return self._DoCall("FullOTA_PostValidate")
2889
Doug Zongker05d3dea2009-06-22 11:32:31 -07002890 def IncrementalOTA_Assertions(self):
2891 """Called after emitting the block of assertions at the top of an
2892 incremental OTA package. Implementations can add whatever
2893 additional assertions they like."""
2894 return self._DoCall("IncrementalOTA_Assertions")
2895
Doug Zongkere5ff5902012-01-17 10:55:37 -08002896 def IncrementalOTA_VerifyBegin(self):
2897 """Called at the start of the verification phase of incremental
2898 OTA installation; additional checks can be placed here to abort
2899 the script before any changes are made."""
2900 return self._DoCall("IncrementalOTA_VerifyBegin")
2901
Doug Zongker05d3dea2009-06-22 11:32:31 -07002902 def IncrementalOTA_VerifyEnd(self):
2903 """Called at the end of the verification phase of incremental OTA
2904 installation; additional checks can be placed here to abort the
2905 script before any changes are made."""
2906 return self._DoCall("IncrementalOTA_VerifyEnd")
2907
Doug Zongkere5ff5902012-01-17 10:55:37 -08002908 def IncrementalOTA_InstallBegin(self):
2909 """Called at the start of incremental OTA installation (after
2910 verification is complete)."""
2911 return self._DoCall("IncrementalOTA_InstallBegin")
2912
Yifan Hong10c530d2018-12-27 17:34:18 -08002913 def IncrementalOTA_GetBlockDifferences(self):
2914 """Called during incremental OTA installation and verification.
2915 Implementation should return a list of BlockDifference objects describing
2916 the update on each additional partitions.
2917 """
2918 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2919
Doug Zongker05d3dea2009-06-22 11:32:31 -07002920 def IncrementalOTA_InstallEnd(self):
2921 """Called at the end of incremental OTA installation; typically
2922 this is used to install the image for the device's baseband
2923 processor."""
2924 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002925
Tao Bao9bc6bb22015-11-09 16:58:28 -08002926 def VerifyOTA_Assertions(self):
2927 return self._DoCall("VerifyOTA_Assertions")
2928
Tao Bao76def242017-11-21 09:25:31 -08002929
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002930class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002931 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002932 self.name = name
2933 self.data = data
2934 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002935 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002936 self.sha1 = sha1(data).hexdigest()
2937
2938 @classmethod
2939 def FromLocalFile(cls, name, diskname):
2940 f = open(diskname, "rb")
2941 data = f.read()
2942 f.close()
2943 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002944
2945 def WriteToTemp(self):
2946 t = tempfile.NamedTemporaryFile()
2947 t.write(self.data)
2948 t.flush()
2949 return t
2950
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002951 def WriteToDir(self, d):
2952 with open(os.path.join(d, self.name), "wb") as fp:
2953 fp.write(self.data)
2954
Geremy Condra36bd3652014-02-06 19:45:10 -08002955 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002956 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002957
Tao Bao76def242017-11-21 09:25:31 -08002958
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002959DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002960 ".gz": "imgdiff",
2961 ".zip": ["imgdiff", "-z"],
2962 ".jar": ["imgdiff", "-z"],
2963 ".apk": ["imgdiff", "-z"],
2964 ".img": "imgdiff",
2965}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002966
Tao Bao76def242017-11-21 09:25:31 -08002967
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002968class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002969 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002970 self.tf = tf
2971 self.sf = sf
2972 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002973 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002974
2975 def ComputePatch(self):
2976 """Compute the patch (as a string of data) needed to turn sf into
2977 tf. Returns the same tuple as GetPatch()."""
2978
2979 tf = self.tf
2980 sf = self.sf
2981
Doug Zongker24cd2802012-08-14 16:36:15 -07002982 if self.diff_program:
2983 diff_program = self.diff_program
2984 else:
2985 ext = os.path.splitext(tf.name)[1]
2986 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002987
2988 ttemp = tf.WriteToTemp()
2989 stemp = sf.WriteToTemp()
2990
2991 ext = os.path.splitext(tf.name)[1]
2992
2993 try:
2994 ptemp = tempfile.NamedTemporaryFile()
2995 if isinstance(diff_program, list):
2996 cmd = copy.copy(diff_program)
2997 else:
2998 cmd = [diff_program]
2999 cmd.append(stemp.name)
3000 cmd.append(ttemp.name)
3001 cmd.append(ptemp.name)
3002 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07003003 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04003004
Doug Zongkerf8340082014-08-05 10:39:37 -07003005 def run():
3006 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07003007 if e:
3008 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07003009 th = threading.Thread(target=run)
3010 th.start()
3011 th.join(timeout=300) # 5 mins
3012 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07003013 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07003014 p.terminate()
3015 th.join(5)
3016 if th.is_alive():
3017 p.kill()
3018 th.join()
3019
Tianjie Xua2a9f992018-01-05 15:15:54 -08003020 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07003021 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07003022 self.patch = None
3023 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003024 diff = ptemp.read()
3025 finally:
3026 ptemp.close()
3027 stemp.close()
3028 ttemp.close()
3029
3030 self.patch = diff
3031 return self.tf, self.sf, self.patch
3032
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003033 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08003034 """Returns a tuple of (target_file, source_file, patch_data).
3035
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003036 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08003037 computing the patch failed.
3038 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003039 return self.tf, self.sf, self.patch
3040
3041
3042def ComputeDifferences(diffs):
3043 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07003044 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003045
3046 # Do the largest files first, to try and reduce the long-pole effect.
3047 by_size = [(i.tf.size, i) for i in diffs]
3048 by_size.sort(reverse=True)
3049 by_size = [i[1] for i in by_size]
3050
3051 lock = threading.Lock()
3052 diff_iter = iter(by_size) # accessed under lock
3053
3054 def worker():
3055 try:
3056 lock.acquire()
3057 for d in diff_iter:
3058 lock.release()
3059 start = time.time()
3060 d.ComputePatch()
3061 dur = time.time() - start
3062 lock.acquire()
3063
3064 tf, sf, patch = d.GetPatch()
3065 if sf.name == tf.name:
3066 name = tf.name
3067 else:
3068 name = "%s (%s)" % (tf.name, sf.name)
3069 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07003070 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003071 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07003072 logger.info(
3073 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
3074 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003075 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07003076 except Exception:
3077 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07003078 raise
3079
3080 # start worker threads; wait for them all to finish.
3081 threads = [threading.Thread(target=worker)
3082 for i in range(OPTIONS.worker_threads)]
3083 for th in threads:
3084 th.start()
3085 while threads:
3086 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07003087
3088
Dan Albert8b72aef2015-03-23 19:13:21 -07003089class BlockDifference(object):
3090 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07003091 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003092 self.tgt = tgt
3093 self.src = src
3094 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003095 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07003096 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003097
Tao Baodd2a5892015-03-12 12:32:37 -07003098 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08003099 version = max(
3100 int(i) for i in
3101 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08003102 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07003103 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07003104
Tianjie Xu41976c72019-07-03 13:57:01 -07003105 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
3106 version=self.version,
3107 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08003108 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003109 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08003110 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07003111 self.touched_src_ranges = b.touched_src_ranges
3112 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003113
Yifan Hong10c530d2018-12-27 17:34:18 -08003114 # On devices with dynamic partitions, for new partitions,
3115 # src is None but OPTIONS.source_info_dict is not.
3116 if OPTIONS.source_info_dict is None:
3117 is_dynamic_build = OPTIONS.info_dict.get(
3118 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003119 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07003120 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08003121 is_dynamic_build = OPTIONS.source_info_dict.get(
3122 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08003123 is_dynamic_source = partition in shlex.split(
3124 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08003125
Yifan Hongbb2658d2019-01-25 12:30:58 -08003126 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08003127 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
3128
Yifan Hongbb2658d2019-01-25 12:30:58 -08003129 # For dynamic partitions builds, check partition list in both source
3130 # and target build because new partitions may be added, and existing
3131 # partitions may be removed.
3132 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
3133
Yifan Hong10c530d2018-12-27 17:34:18 -08003134 if is_dynamic:
3135 self.device = 'map_partition("%s")' % partition
3136 else:
3137 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003138 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3139 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003140 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003141 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3142 OPTIONS.source_info_dict)
3143 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003144
Tao Baod8d14be2016-02-04 14:26:02 -08003145 @property
3146 def required_cache(self):
3147 return self._required_cache
3148
Tao Bao76def242017-11-21 09:25:31 -08003149 def WriteScript(self, script, output_zip, progress=None,
3150 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003151 if not self.src:
3152 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003153 script.Print("Patching %s image unconditionally..." % (self.partition,))
3154 else:
3155 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003156
Dan Albert8b72aef2015-03-23 19:13:21 -07003157 if progress:
3158 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003159 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003160
3161 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003162 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003163
Tao Bao9bc6bb22015-11-09 16:58:28 -08003164 def WriteStrictVerifyScript(self, script):
3165 """Verify all the blocks in the care_map, including clobbered blocks.
3166
3167 This differs from the WriteVerifyScript() function: a) it prints different
3168 error messages; b) it doesn't allow half-way updated images to pass the
3169 verification."""
3170
3171 partition = self.partition
3172 script.Print("Verifying %s..." % (partition,))
3173 ranges = self.tgt.care_map
3174 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003175 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003176 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3177 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003178 self.device, ranges_str,
3179 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003180 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003181 script.AppendExtra("")
3182
Tao Baod522bdc2016-04-12 15:53:16 -07003183 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003184 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003185
3186 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003187 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003188 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003189
3190 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003191 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003192 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003193 ranges = self.touched_src_ranges
3194 expected_sha1 = self.touched_src_sha1
3195 else:
3196 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3197 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003198
3199 # No blocks to be checked, skipping.
3200 if not ranges:
3201 return
3202
Tao Bao5ece99d2015-05-12 11:42:31 -07003203 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003204 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003205 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003206 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3207 '"%s.patch.dat")) then' % (
3208 self.device, ranges_str, expected_sha1,
3209 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003210 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003211 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003212
Tianjie Xufc3422a2015-12-15 11:53:59 -08003213 if self.version >= 4:
3214
3215 # Bug: 21124327
3216 # When generating incrementals for the system and vendor partitions in
3217 # version 4 or newer, explicitly check the first block (which contains
3218 # the superblock) of the partition to see if it's what we expect. If
3219 # this check fails, give an explicit log message about the partition
3220 # having been remounted R/W (the most likely explanation).
3221 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003222 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003223
3224 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003225 if partition == "system":
3226 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3227 else:
3228 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003229 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003230 'ifelse (block_image_recover({device}, "{ranges}") && '
3231 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003232 'package_extract_file("{partition}.transfer.list"), '
3233 '"{partition}.new.dat", "{partition}.patch.dat"), '
3234 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003235 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003236 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003237 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003238
Tao Baodd2a5892015-03-12 12:32:37 -07003239 # Abort the OTA update. Note that the incremental OTA cannot be applied
3240 # even if it may match the checksum of the target partition.
3241 # a) If version < 3, operations like move and erase will make changes
3242 # unconditionally and damage the partition.
3243 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003244 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003245 if partition == "system":
3246 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3247 else:
3248 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3249 script.AppendExtra((
3250 'abort("E%d: %s partition has unexpected contents");\n'
3251 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003252
Yifan Hong10c530d2018-12-27 17:34:18 -08003253 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003254 partition = self.partition
3255 script.Print('Verifying the updated %s image...' % (partition,))
3256 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3257 ranges = self.tgt.care_map
3258 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003259 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003260 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003261 self.device, ranges_str,
3262 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003263
3264 # Bug: 20881595
3265 # Verify that extended blocks are really zeroed out.
3266 if self.tgt.extended:
3267 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003268 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003269 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003270 self.device, ranges_str,
3271 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003272 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003273 if partition == "system":
3274 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3275 else:
3276 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003277 script.AppendExtra(
3278 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003279 ' abort("E%d: %s partition has unexpected non-zero contents after '
3280 'OTA update");\n'
3281 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003282 else:
3283 script.Print('Verified the updated %s image.' % (partition,))
3284
Tianjie Xu209db462016-05-24 17:34:52 -07003285 if partition == "system":
3286 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3287 else:
3288 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3289
Tao Bao5fcaaef2015-06-01 13:40:49 -07003290 script.AppendExtra(
3291 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003292 ' abort("E%d: %s partition has unexpected contents after OTA '
3293 'update");\n'
3294 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003295
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003296 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003297 ZipWrite(output_zip,
3298 '{}.transfer.list'.format(self.path),
3299 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003300
Tao Bao76def242017-11-21 09:25:31 -08003301 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3302 # its size. Quailty 9 almost triples the compression time but doesn't
3303 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003304 # zip | brotli(quality 6) | brotli(quality 9)
3305 # compressed_size: 942M | 869M (~8% reduced) | 854M
3306 # compression_time: 75s | 265s | 719s
3307 # decompression_time: 15s | 25s | 25s
3308
3309 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003310 brotli_cmd = ['brotli', '--quality=6',
3311 '--output={}.new.dat.br'.format(self.path),
3312 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003313 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003314 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003315
3316 new_data_name = '{}.new.dat.br'.format(self.partition)
3317 ZipWrite(output_zip,
3318 '{}.new.dat.br'.format(self.path),
3319 new_data_name,
3320 compress_type=zipfile.ZIP_STORED)
3321 else:
3322 new_data_name = '{}.new.dat'.format(self.partition)
3323 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3324
Dan Albert8e0178d2015-01-27 15:53:15 -08003325 ZipWrite(output_zip,
3326 '{}.patch.dat'.format(self.path),
3327 '{}.patch.dat'.format(self.partition),
3328 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003329
Tianjie Xu209db462016-05-24 17:34:52 -07003330 if self.partition == "system":
3331 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3332 else:
3333 code = ErrorCode.VENDOR_UPDATE_FAILURE
3334
Yifan Hong10c530d2018-12-27 17:34:18 -08003335 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003336 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003337 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003338 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003339 device=self.device, partition=self.partition,
3340 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003341 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003342
Kelvin Zhang0876c412020-06-23 15:06:58 -04003343 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003344 data = source.ReadRangeSet(ranges)
3345 ctx = sha1()
3346
3347 for p in data:
3348 ctx.update(p)
3349
3350 return ctx.hexdigest()
3351
Kelvin Zhang0876c412020-06-23 15:06:58 -04003352 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003353 """Return the hash value for all zero blocks."""
3354 zero_block = '\x00' * 4096
3355 ctx = sha1()
3356 for _ in range(num_blocks):
3357 ctx.update(zero_block)
3358
3359 return ctx.hexdigest()
3360
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003361
Tianjie Xu41976c72019-07-03 13:57:01 -07003362# Expose these two classes to support vendor-specific scripts
3363DataImage = images.DataImage
3364EmptyImage = images.EmptyImage
3365
Tao Bao76def242017-11-21 09:25:31 -08003366
Doug Zongker96a57e72010-09-26 14:57:41 -07003367# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003368PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003369 "ext4": "EMMC",
3370 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003371 "f2fs": "EMMC",
3372 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003373}
Doug Zongker96a57e72010-09-26 14:57:41 -07003374
Kelvin Zhang0876c412020-06-23 15:06:58 -04003375
Yifan Hongbdb32012020-05-07 12:38:53 -07003376def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3377 """
3378 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3379 backwards compatibility. It aborts if the fstab entry has slotselect option
3380 (unless check_no_slot is explicitly set to False).
3381 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003382 fstab = info["fstab"]
3383 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003384 if check_no_slot:
3385 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003386 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003387 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3388 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003389 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003390
3391
Yifan Hongbdb32012020-05-07 12:38:53 -07003392def GetTypeAndDeviceExpr(mount_point, info):
3393 """
3394 Return the filesystem of the partition, and an edify expression that evaluates
3395 to the device at runtime.
3396 """
3397 fstab = info["fstab"]
3398 if fstab:
3399 p = fstab[mount_point]
3400 device_expr = '"%s"' % fstab[mount_point].device
3401 if p.slotselect:
3402 device_expr = 'add_slot_suffix(%s)' % device_expr
3403 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003404 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003405
3406
3407def GetEntryForDevice(fstab, device):
3408 """
3409 Returns:
3410 The first entry in fstab whose device is the given value.
3411 """
3412 if not fstab:
3413 return None
3414 for mount_point in fstab:
3415 if fstab[mount_point].device == device:
3416 return fstab[mount_point]
3417 return None
3418
Kelvin Zhang0876c412020-06-23 15:06:58 -04003419
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003420def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003421 """Parses and converts a PEM-encoded certificate into DER-encoded.
3422
3423 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3424
3425 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003426 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003427 """
3428 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003429 save = False
3430 for line in data.split("\n"):
3431 if "--END CERTIFICATE--" in line:
3432 break
3433 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003434 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003435 if "--BEGIN CERTIFICATE--" in line:
3436 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003437 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003438 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003439
Tao Bao04e1f012018-02-04 12:13:35 -08003440
3441def ExtractPublicKey(cert):
3442 """Extracts the public key (PEM-encoded) from the given certificate file.
3443
3444 Args:
3445 cert: The certificate filename.
3446
3447 Returns:
3448 The public key string.
3449
3450 Raises:
3451 AssertionError: On non-zero return from 'openssl'.
3452 """
3453 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3454 # While openssl 1.1 writes the key into the given filename followed by '-out',
3455 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3456 # stdout instead.
3457 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3458 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3459 pubkey, stderrdata = proc.communicate()
3460 assert proc.returncode == 0, \
3461 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3462 return pubkey
3463
3464
Tao Bao1ac886e2019-06-26 11:58:22 -07003465def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003466 """Extracts the AVB public key from the given public or private key.
3467
3468 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003469 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003470 key: The input key file, which should be PEM-encoded public or private key.
3471
3472 Returns:
3473 The path to the extracted AVB public key file.
3474 """
3475 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3476 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003477 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003478 return output
3479
3480
Doug Zongker412c02f2014-02-13 10:58:24 -08003481def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3482 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003483 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003484
Tao Bao6d5d6232018-03-09 17:04:42 -08003485 Most of the space in the boot and recovery images is just the kernel, which is
3486 identical for the two, so the resulting patch should be efficient. Add it to
3487 the output zip, along with a shell script that is run from init.rc on first
3488 boot to actually do the patching and install the new recovery image.
3489
3490 Args:
3491 input_dir: The top-level input directory of the target-files.zip.
3492 output_sink: The callback function that writes the result.
3493 recovery_img: File object for the recovery image.
3494 boot_img: File objects for the boot image.
3495 info_dict: A dict returned by common.LoadInfoDict() on the input
3496 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003497 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003498 if info_dict is None:
3499 info_dict = OPTIONS.info_dict
3500
Tao Bao6d5d6232018-03-09 17:04:42 -08003501 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003502 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3503
3504 if board_uses_vendorimage:
3505 # In this case, the output sink is rooted at VENDOR
3506 recovery_img_path = "etc/recovery.img"
3507 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
Bill Peckhame868aec2019-09-17 17:06:47 -07003508 else:
3509 # In this case the output sink is rooted at SYSTEM
3510 recovery_img_path = "vendor/etc/recovery.img"
3511 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
Doug Zongkerc9253822014-02-04 12:17:58 -08003512
Tao Baof2cffbd2015-07-22 12:33:18 -07003513 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003514 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003515
3516 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003517 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003518 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003519 # With system-root-image, boot and recovery images will have mismatching
3520 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3521 # to handle such a case.
3522 if system_root_image:
3523 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003524 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003525 assert not os.path.exists(path)
3526 else:
3527 diff_program = ["imgdiff"]
3528 if os.path.exists(path):
3529 diff_program.append("-b")
3530 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003531 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003532 else:
3533 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003534
3535 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3536 _, _, patch = d.ComputePatch()
3537 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003538
Dan Albertebb19aa2015-03-27 19:11:53 -07003539 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003540 # The following GetTypeAndDevice()s need to use the path in the target
3541 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003542 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3543 check_no_slot=False)
3544 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3545 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003546 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003547 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003548
Tao Baof2cffbd2015-07-22 12:33:18 -07003549 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003550
3551 # Note that we use /vendor to refer to the recovery resources. This will
3552 # work for a separate vendor partition mounted at /vendor or a
3553 # /system/vendor subdirectory on the system partition, for which init will
3554 # create a symlink from /vendor to /system/vendor.
3555
3556 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003557if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3558 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003559 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003560 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3561 log -t recovery "Installing new recovery image: succeeded" || \\
3562 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003563else
3564 log -t recovery "Recovery image already installed"
3565fi
3566""" % {'type': recovery_type,
3567 'device': recovery_device,
3568 'sha1': recovery_img.sha1,
3569 'size': recovery_img.size}
3570 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003571 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003572if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3573 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003574 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003575 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3576 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3577 log -t recovery "Installing new recovery image: succeeded" || \\
3578 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003579else
3580 log -t recovery "Recovery image already installed"
3581fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003582""" % {'boot_size': boot_img.size,
3583 'boot_sha1': boot_img.sha1,
3584 'recovery_size': recovery_img.size,
3585 'recovery_sha1': recovery_img.sha1,
3586 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003587 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003588 'recovery_type': recovery_type,
3589 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003590 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003591
Bill Peckhame868aec2019-09-17 17:06:47 -07003592 # The install script location moved from /system/etc to /system/bin in the L
3593 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
dianlujitao9b5f7402020-09-12 13:48:26 +08003594 output_sink("bin/install-recovery.sh", sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003595
3596
3597class DynamicPartitionUpdate(object):
3598 def __init__(self, src_group=None, tgt_group=None, progress=None,
3599 block_difference=None):
3600 self.src_group = src_group
3601 self.tgt_group = tgt_group
3602 self.progress = progress
3603 self.block_difference = block_difference
3604
3605 @property
3606 def src_size(self):
3607 if not self.block_difference:
3608 return 0
3609 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3610
3611 @property
3612 def tgt_size(self):
3613 if not self.block_difference:
3614 return 0
3615 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3616
3617 @staticmethod
3618 def _GetSparseImageSize(img):
3619 if not img:
3620 return 0
3621 return img.blocksize * img.total_blocks
3622
3623
3624class DynamicGroupUpdate(object):
3625 def __init__(self, src_size=None, tgt_size=None):
3626 # None: group does not exist. 0: no size limits.
3627 self.src_size = src_size
3628 self.tgt_size = tgt_size
3629
3630
3631class DynamicPartitionsDifference(object):
3632 def __init__(self, info_dict, block_diffs, progress_dict=None,
Peter Cai17a8ef42020-03-01 14:43:57 +08003633 source_info_dict=None, build_without_vendor=False):
Yifan Hong10c530d2018-12-27 17:34:18 -08003634 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003635 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003636
Peter Cai17a8ef42020-03-01 14:43:57 +08003637 self._build_without_vendor = build_without_vendor
Yifan Hong10c530d2018-12-27 17:34:18 -08003638 self._remove_all_before_apply = False
3639 if source_info_dict is None:
3640 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003641 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003642
Tao Baof1113e92019-06-18 12:10:14 -07003643 block_diff_dict = collections.OrderedDict(
3644 [(e.partition, e) for e in block_diffs])
3645
Yifan Hong10c530d2018-12-27 17:34:18 -08003646 assert len(block_diff_dict) == len(block_diffs), \
3647 "Duplicated BlockDifference object for {}".format(
3648 [partition for partition, count in
3649 collections.Counter(e.partition for e in block_diffs).items()
3650 if count > 1])
3651
Yifan Hong79997e52019-01-23 16:56:19 -08003652 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003653
3654 for p, block_diff in block_diff_dict.items():
3655 self._partition_updates[p] = DynamicPartitionUpdate()
3656 self._partition_updates[p].block_difference = block_diff
3657
3658 for p, progress in progress_dict.items():
3659 if p in self._partition_updates:
3660 self._partition_updates[p].progress = progress
3661
3662 tgt_groups = shlex.split(info_dict.get(
3663 "super_partition_groups", "").strip())
3664 src_groups = shlex.split(source_info_dict.get(
3665 "super_partition_groups", "").strip())
3666
3667 for g in tgt_groups:
3668 for p in shlex.split(info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003669 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003670 assert p in self._partition_updates, \
3671 "{} is in target super_{}_partition_list but no BlockDifference " \
3672 "object is provided.".format(p, g)
3673 self._partition_updates[p].tgt_group = g
3674
3675 for g in src_groups:
3676 for p in shlex.split(source_info_dict.get(
Kelvin Zhang563750f2021-04-28 12:46:17 -04003677 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003678 assert p in self._partition_updates, \
3679 "{} is in source super_{}_partition_list but no BlockDifference " \
3680 "object is provided.".format(p, g)
3681 self._partition_updates[p].src_group = g
3682
Yifan Hong45433e42019-01-18 13:55:25 -08003683 target_dynamic_partitions = set(shlex.split(info_dict.get(
3684 "dynamic_partition_list", "").strip()))
3685 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3686 if u.tgt_size)
3687 assert block_diffs_with_target == target_dynamic_partitions, \
3688 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3689 list(target_dynamic_partitions), list(block_diffs_with_target))
3690
3691 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3692 "dynamic_partition_list", "").strip()))
3693 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3694 if u.src_size)
3695 assert block_diffs_with_source == source_dynamic_partitions, \
3696 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3697 list(source_dynamic_partitions), list(block_diffs_with_source))
3698
Yifan Hong10c530d2018-12-27 17:34:18 -08003699 if self._partition_updates:
3700 logger.info("Updating dynamic partitions %s",
3701 self._partition_updates.keys())
3702
Yifan Hong79997e52019-01-23 16:56:19 -08003703 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003704
3705 for g in tgt_groups:
3706 self._group_updates[g] = DynamicGroupUpdate()
3707 self._group_updates[g].tgt_size = int(info_dict.get(
3708 "super_%s_group_size" % g, "0").strip())
3709
3710 for g in src_groups:
3711 if g not in self._group_updates:
3712 self._group_updates[g] = DynamicGroupUpdate()
3713 self._group_updates[g].src_size = int(source_info_dict.get(
3714 "super_%s_group_size" % g, "0").strip())
3715
3716 self._Compute()
3717
3718 def WriteScript(self, script, output_zip, write_verify_script=False):
3719 script.Comment('--- Start patching dynamic partitions ---')
3720 for p, u in self._partition_updates.items():
3721 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3722 script.Comment('Patch partition %s' % p)
3723 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3724 write_verify_script=False)
3725
3726 op_list_path = MakeTempFile()
3727 with open(op_list_path, 'w') as f:
3728 for line in self._op_list:
3729 f.write('{}\n'.format(line))
3730
3731 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3732
3733 script.Comment('Update dynamic partition metadata')
3734 script.AppendExtra('assert(update_dynamic_partitions('
3735 'package_extract_file("dynamic_partitions_op_list")));')
3736
3737 if write_verify_script:
3738 for p, u in self._partition_updates.items():
3739 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3740 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003741 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003742
3743 for p, u in self._partition_updates.items():
3744 if u.tgt_size and u.src_size <= u.tgt_size:
3745 script.Comment('Patch partition %s' % p)
3746 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3747 write_verify_script=write_verify_script)
3748 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003749 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003750
3751 script.Comment('--- End patching dynamic partitions ---')
3752
3753 def _Compute(self):
3754 self._op_list = list()
3755
3756 def append(line):
3757 self._op_list.append(line)
3758
3759 def comment(line):
3760 self._op_list.append("# %s" % line)
3761
Peter Cai17a8ef42020-03-01 14:43:57 +08003762 if self._build_without_vendor:
3763 comment('System-only build, keep original vendor partition')
3764 # When building without vendor, we do not want to override
3765 # any partition already existing. In this case, we can only
3766 # resize, but not remove / create / re-create any other
3767 # partition.
3768 for p, u in self._partition_updates.items():
3769 comment('Resize partition %s to %s' % (p, u.tgt_size))
3770 append('resize %s %s' % (p, u.tgt_size))
3771 return
3772
Yifan Hong10c530d2018-12-27 17:34:18 -08003773 if self._remove_all_before_apply:
3774 comment('Remove all existing dynamic partitions and groups before '
3775 'applying full OTA')
3776 append('remove_all_groups')
3777
3778 for p, u in self._partition_updates.items():
3779 if u.src_group and not u.tgt_group:
3780 append('remove %s' % p)
3781
3782 for p, u in self._partition_updates.items():
3783 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3784 comment('Move partition %s from %s to default' % (p, u.src_group))
3785 append('move %s default' % p)
3786
3787 for p, u in self._partition_updates.items():
3788 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3789 comment('Shrink partition %s from %d to %d' %
3790 (p, u.src_size, u.tgt_size))
3791 append('resize %s %s' % (p, u.tgt_size))
3792
3793 for g, u in self._group_updates.items():
3794 if u.src_size is not None and u.tgt_size is None:
3795 append('remove_group %s' % g)
3796 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003797 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003798 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3799 append('resize_group %s %d' % (g, u.tgt_size))
3800
3801 for g, u in self._group_updates.items():
3802 if u.src_size is None and u.tgt_size is not None:
3803 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3804 append('add_group %s %d' % (g, u.tgt_size))
3805 if (u.src_size is not None and u.tgt_size is not None and
Kelvin Zhang563750f2021-04-28 12:46:17 -04003806 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003807 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3808 append('resize_group %s %d' % (g, u.tgt_size))
3809
3810 for p, u in self._partition_updates.items():
3811 if u.tgt_group and not u.src_group:
3812 comment('Add partition %s to group %s' % (p, u.tgt_group))
3813 append('add %s %s' % (p, u.tgt_group))
3814
3815 for p, u in self._partition_updates.items():
3816 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003817 comment('Grow partition %s from %d to %d' %
3818 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003819 append('resize %s %d' % (p, u.tgt_size))
3820
3821 for p, u in self._partition_updates.items():
3822 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3823 comment('Move partition %s from default to %s' %
3824 (p, u.tgt_group))
3825 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003826
3827
jiajia tangf3f842b2021-03-17 21:49:44 +08003828def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003829 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003830 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003831
3832 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003833 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003834
3835 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003836 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003837 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003838 tmp_dir = MakeTempDir('boot_', suffix='.img')
3839 try:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003840 RunAndCheckOutput(['unpack_bootimg', '--boot_img',
3841 boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003842 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3843 if not os.path.isfile(ramdisk):
3844 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3845 return None
3846 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003847 if ramdisk_format == RamdiskFormat.LZ4:
3848 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3849 elif ramdisk_format == RamdiskFormat.GZ:
3850 with open(ramdisk, 'rb') as input_stream:
3851 with open(uncompressed_ramdisk, 'wb') as output_stream:
Kelvin Zhang563750f2021-04-28 12:46:17 -04003852 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(),
3853 stdout=output_stream.fileno())
jiajia tangf3f842b2021-03-17 21:49:44 +08003854 p2.wait()
3855 else:
3856 logger.error('Only support lz4 or minigzip ramdisk format.')
3857 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003858
3859 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3860 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3861 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3862 # the host environment.
3863 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Kelvin Zhang563750f2021-04-28 12:46:17 -04003864 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003865
Yifan Hongc65a0542021-01-07 14:21:01 -08003866 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3867 prop_file = os.path.join(extracted_ramdisk, search_path)
3868 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003869 return prop_file
Kelvin Zhang563750f2021-04-28 12:46:17 -04003870 logger.warning(
3871 'Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003872
Yifan Hong7dc51172021-01-12 11:27:39 -08003873 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003874
Yifan Hong85ac5012021-01-07 14:43:46 -08003875 except ExternalError as e:
3876 logger.warning('Unable to get boot image build props: %s', e)
3877 return None
3878
3879
3880def GetBootImageTimestamp(boot_img):
3881 """
3882 Get timestamp from ramdisk within the boot image
3883
3884 Args:
3885 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3886
3887 Return:
3888 An integer that corresponds to the timestamp of the boot image, or None
3889 if file has unknown format. Raise exception if an unexpected error has
3890 occurred.
3891 """
3892 prop_file = GetBootImageBuildProp(boot_img)
3893 if not prop_file:
3894 return None
3895
3896 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3897 if props is None:
3898 return None
3899
3900 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003901 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3902 if timestamp:
3903 return int(timestamp)
Kelvin Zhang563750f2021-04-28 12:46:17 -04003904 logger.warning(
3905 'Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003906 return None
3907
3908 except ExternalError as e:
3909 logger.warning('Unable to get boot image timestamp: %s', e)
3910 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003911
3912
3913def GetCareMap(which, imgname):
3914 """Returns the care_map string for the given partition.
3915
3916 Args:
3917 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3918 imgname: The filename of the image.
3919
3920 Returns:
3921 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3922 RangeSet; or None.
3923 """
3924 assert which in PARTITIONS_WITH_CARE_MAP
3925
3926 # which + "_image_size" contains the size that the actual filesystem image
3927 # resides in, which is all that needs to be verified. The additional blocks in
3928 # the image file contain verity metadata, by reading which would trigger
3929 # invalid reads.
3930 image_size = OPTIONS.info_dict.get(which + "_image_size")
3931 if not image_size:
3932 return None
3933
3934 image_blocks = int(image_size) // 4096 - 1
3935 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3936
3937 # For sparse images, we will only check the blocks that are listed in the care
3938 # map, i.e. the ones with meaningful data.
3939 if "extfs_sparse_flag" in OPTIONS.info_dict:
3940 simg = sparse_img.SparseImage(imgname)
3941 care_map_ranges = simg.care_map.intersect(
3942 rangelib.RangeSet("0-{}".format(image_blocks)))
3943
3944 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3945 # image.
3946 else:
3947 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3948
3949 return [which, care_map_ranges.to_string_raw()]
3950
3951
3952def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3953 """Generates and adds care_map.pb for a/b partition that has care_map.
3954
3955 Args:
3956 output_file: The output zip file (needs to be already open),
3957 or file path to write care_map.pb.
3958 ab_partitions: The list of A/B partitions.
3959 image_paths: A map from the partition name to the image path.
3960 """
3961 if not output_file:
3962 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3963
3964 care_map_list = []
3965 for partition in ab_partitions:
3966 partition = partition.strip()
3967 if partition not in PARTITIONS_WITH_CARE_MAP:
3968 continue
3969
3970 verity_block_device = "{}_verity_block_device".format(partition)
3971 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3972 if (verity_block_device in OPTIONS.info_dict or
3973 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3974 if partition not in image_paths:
3975 logger.warning('Potential partition with care_map missing from images: %s',
3976 partition)
3977 continue
3978 image_path = image_paths[partition]
3979 if not os.path.exists(image_path):
3980 raise ExternalError('Expected image at path {}'.format(image_path))
3981
3982 care_map = GetCareMap(partition, image_path)
3983 if not care_map:
3984 continue
3985 care_map_list += care_map
3986
3987 # adds fingerprint field to the care_map
3988 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3989 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3990 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3991 "ro.{}.build.thumbprint".format(partition)]
3992
3993 present_props = [x for x in prop_name_list if
3994 partition_props and partition_props.GetProp(x)]
3995 if not present_props:
3996 logger.warning(
3997 "fingerprint is not present for partition %s", partition)
3998 property_id, fingerprint = "unknown", "unknown"
3999 else:
4000 property_id = present_props[0]
4001 fingerprint = partition_props.GetProp(property_id)
4002 care_map_list += [property_id, fingerprint]
4003
4004 if not care_map_list:
4005 return
4006
4007 # Converts the list into proto buf message by calling care_map_generator; and
4008 # writes the result to a temp file.
4009 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
4010 suffix=".txt")
4011 with open(temp_care_map_text, 'w') as text_file:
4012 text_file.write('\n'.join(care_map_list))
4013
4014 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
4015 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
4016 RunAndCheckOutput(care_map_gen_cmd)
4017
4018 if not isinstance(output_file, zipfile.ZipFile):
4019 shutil.copy(temp_care_map, output_file)
4020 return
4021 # output_file is a zip file
4022 care_map_path = "META/care_map.pb"
4023 if care_map_path in output_file.namelist():
4024 # Copy the temp file into the OPTIONS.input_tmp dir and update the
4025 # replace_updated_files_list used by add_img_to_target_files
4026 if not OPTIONS.replace_updated_files_list:
4027 OPTIONS.replace_updated_files_list = []
4028 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
4029 OPTIONS.replace_updated_files_list.append(care_map_path)
4030 else:
4031 ZipWrite(output_file, temp_care_map, arcname=care_map_path)