blob: 0683678b50070d18b1cad682d122c346a985aa80 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
131
Tianjie Xu209db462016-05-24 17:34:52 -0700132class ErrorCode(object):
133 """Define error_codes for failures that happen during the actual
134 update package installation.
135
136 Error codes 0-999 are reserved for failures before the package
137 installation (i.e. low battery, package verification failure).
138 Detailed code in 'bootable/recovery/error_code.h' """
139
140 SYSTEM_VERIFICATION_FAILURE = 1000
141 SYSTEM_UPDATE_FAILURE = 1001
142 SYSTEM_UNEXPECTED_CONTENTS = 1002
143 SYSTEM_NONZERO_CONTENTS = 1003
144 SYSTEM_RECOVER_FAILURE = 1004
145 VENDOR_VERIFICATION_FAILURE = 2000
146 VENDOR_UPDATE_FAILURE = 2001
147 VENDOR_UNEXPECTED_CONTENTS = 2002
148 VENDOR_NONZERO_CONTENTS = 2003
149 VENDOR_RECOVER_FAILURE = 2004
150 OEM_PROP_MISMATCH = 3000
151 FINGERPRINT_MISMATCH = 3001
152 THUMBPRINT_MISMATCH = 3002
153 OLDER_BUILD = 3003
154 DEVICE_MISMATCH = 3004
155 BAD_PATCH_FILE = 3005
156 INSUFFICIENT_CACHE_SPACE = 3006
157 TUNE_PARTITION_FAILURE = 3007
158 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800159
Tao Bao80921982018-03-21 21:02:19 -0700160
Dan Albert8b72aef2015-03-23 19:13:21 -0700161class ExternalError(RuntimeError):
162 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700163
164
Tao Bao32fcdab2018-10-12 10:30:39 -0700165def InitLogging():
166 DEFAULT_LOGGING_CONFIG = {
167 'version': 1,
168 'disable_existing_loggers': False,
169 'formatters': {
170 'standard': {
171 'format':
172 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
173 'datefmt': '%Y-%m-%d %H:%M:%S',
174 },
175 },
176 'handlers': {
177 'default': {
178 'class': 'logging.StreamHandler',
179 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700180 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 },
182 },
183 'loggers': {
184 '': {
185 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700187 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 }
189 }
190 }
191 env_config = os.getenv('LOGGING_CONFIG')
192 if env_config:
193 with open(env_config) as f:
194 config = json.load(f)
195 else:
196 config = DEFAULT_LOGGING_CONFIG
197
198 # Increase the logging level for verbose mode.
199 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700200 config = copy.deepcopy(config)
201 config['handlers']['default']['level'] = 'INFO'
202
203 if OPTIONS.logfile:
204 config = copy.deepcopy(config)
205 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400206 'class': 'logging.FileHandler',
207 'formatter': 'standard',
208 'level': 'INFO',
209 'mode': 'w',
210 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700211 }
212 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700213
214 logging.config.dictConfig(config)
215
216
Yifan Hong8e332ff2020-07-29 17:51:55 -0700217def SetHostToolLocation(tool_name, location):
218 OPTIONS.host_tools[tool_name] = location
219
220
Tao Bao39451582017-05-04 11:10:47 -0700221def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700222 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700223
Tao Bao73dd4f42018-10-04 16:25:33 -0700224 Args:
225 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 verbose: Whether the commands should be shown. Default to the global
227 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700228 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
229 stdin, etc. stdout and stderr will default to subprocess.PIPE and
230 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800231 universal_newlines will default to True, as most of the users in
232 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700233
234 Returns:
235 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700236 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700237 if 'stdout' not in kwargs and 'stderr' not in kwargs:
238 kwargs['stdout'] = subprocess.PIPE
239 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800240 if 'universal_newlines' not in kwargs:
241 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700242
243 # If explicitly set host tool location before, use that location to avoid
244 # PATH violation. Make a copy of args in case client relies on the content
245 # of args later.
246 if args and args[0] in OPTIONS.host_tools:
247 args = args[:]
248 args[0] = OPTIONS.host_tools[args[0]]
249
Tao Bao32fcdab2018-10-12 10:30:39 -0700250 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400251 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700252 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700253 return subprocess.Popen(args, **kwargs)
254
255
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800256def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800257 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800258
259 Args:
260 args: The command represented as a list of strings.
261 verbose: Whether the commands should be shown. Default to the global
262 verbosity if unspecified.
263 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
264 stdin, etc. stdout and stderr will default to subprocess.PIPE and
265 subprocess.STDOUT respectively unless caller specifies any of them.
266
Bill Peckham889b0c62019-02-21 18:53:37 -0800267 Raises:
268 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800269 """
270 proc = Run(args, verbose=verbose, **kwargs)
271 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800272
273 if proc.returncode != 0:
274 raise ExternalError(
275 "Failed to run command '{}' (exit code {})".format(
276 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
403 for partition in PARTITIONS_WITH_CARE_MAP:
404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
411 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
459 # If provided a partition for this property, only look within that
460 # partition's build.prop.
461 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
462 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
463 else:
464 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000465
466 prop_val = self._GetRawBuildProp(prop, partition)
467 if prop_val is not None:
468 return prop_val
469 raise ExternalError("couldn't find %s in %s.build.prop" %
470 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800471
Tao Bao1c320f82019-10-04 23:25:12 -0700472 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800473 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700474 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
475 return self._ResolveRoProductBuildProp(prop)
476
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000477 prop_val = self._GetRawBuildProp(prop, None)
478 if prop_val is not None:
479 return prop_val
480
481 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700482
483 def _ResolveRoProductBuildProp(self, prop):
484 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000485 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700486 if prop_val:
487 return prop_val
488
Steven Laver8e2086e2020-04-27 16:26:31 -0700489 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000490 source_order_val = self._GetRawBuildProp(
491 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700492 if source_order_val:
493 source_order = source_order_val.split(",")
494 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700496
497 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700498 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700499 raise ExternalError(
500 "Invalid ro.product.property_source_order '{}'".format(source_order))
501
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700503 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000504 "ro.product", "ro.product.{}".format(source_partition), 1)
505 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700506 if prop_val:
507 return prop_val
508
509 raise ExternalError("couldn't resolve {}".format(prop))
510
Steven Laver8e2086e2020-04-27 16:26:31 -0700511 def _GetRoProductPropsDefaultSourceOrder(self):
512 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
513 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000516 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700517 if android_version == "10":
518 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
519 # NOTE: float() conversion of android_version will have rounding error.
520 # We are checking for "9" or less, and using "< 10" is well outside of
521 # possible floating point rounding.
522 try:
523 android_version_val = float(android_version)
524 except ValueError:
525 android_version_val = 0
526 if android_version_val < 10:
527 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
528 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
529
Tianjieb37c5be2020-10-15 21:27:10 -0700530 def _GetPlatformVersion(self):
531 version_sdk = self.GetBuildProp("ro.build.version.sdk")
532 # init code switches to version_release_or_codename (see b/158483506). After
533 # API finalization, release_or_codename will be the same as release. This
534 # is the best effort to support pre-S dev stage builds.
535 if int(version_sdk) >= 30:
536 try:
537 return self.GetBuildProp("ro.build.version.release_or_codename")
538 except ExternalError:
539 logger.warning('Failed to find ro.build.version.release_or_codename')
540
541 return self.GetBuildProp("ro.build.version.release")
542
543 def _GetPartitionPlatformVersion(self, partition):
544 try:
545 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
546 partition)
547 except ExternalError:
548 return self.GetPartitionBuildProp("ro.build.version.release",
549 partition)
550
Tao Bao1c320f82019-10-04 23:25:12 -0700551 def GetOemProperty(self, key):
552 if self.oem_props is not None and key in self.oem_props:
553 return self.oem_dicts[0][key]
554 return self.GetBuildProp(key)
555
Daniel Normand5fe8622020-01-08 17:01:11 -0800556 def GetPartitionFingerprint(self, partition):
557 return self._partition_fingerprints.get(partition, None)
558
559 def CalculatePartitionFingerprint(self, partition):
560 try:
561 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
562 except ExternalError:
563 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
564 self.GetPartitionBuildProp("ro.product.brand", partition),
565 self.GetPartitionBuildProp("ro.product.name", partition),
566 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700567 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800568 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400569 self.GetPartitionBuildProp(
570 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800571 self.GetPartitionBuildProp("ro.build.type", partition),
572 self.GetPartitionBuildProp("ro.build.tags", partition))
573
Tao Bao1c320f82019-10-04 23:25:12 -0700574 def CalculateFingerprint(self):
575 if self.oem_props is None:
576 try:
577 return self.GetBuildProp("ro.build.fingerprint")
578 except ExternalError:
579 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
580 self.GetBuildProp("ro.product.brand"),
581 self.GetBuildProp("ro.product.name"),
582 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700583 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700584 self.GetBuildProp("ro.build.id"),
585 self.GetBuildProp("ro.build.version.incremental"),
586 self.GetBuildProp("ro.build.type"),
587 self.GetBuildProp("ro.build.tags"))
588 return "%s/%s/%s:%s" % (
589 self.GetOemProperty("ro.product.brand"),
590 self.GetOemProperty("ro.product.name"),
591 self.GetOemProperty("ro.product.device"),
592 self.GetBuildProp("ro.build.thumbprint"))
593
594 def WriteMountOemScript(self, script):
595 assert self.oem_props is not None
596 recovery_mount_options = self.info_dict.get("recovery_mount_options")
597 script.Mount("/oem", recovery_mount_options)
598
599 def WriteDeviceAssertions(self, script, oem_no_mount):
600 # Read the property directly if not using OEM properties.
601 if not self.oem_props:
602 script.AssertDevice(self.device)
603 return
604
605 # Otherwise assert OEM properties.
606 if not self.oem_dicts:
607 raise ExternalError(
608 "No OEM file provided to answer expected assertions")
609
610 for prop in self.oem_props.split():
611 values = []
612 for oem_dict in self.oem_dicts:
613 if prop in oem_dict:
614 values.append(oem_dict[prop])
615 if not values:
616 raise ExternalError(
617 "The OEM file is missing the property %s" % (prop,))
618 script.AssertOemProperty(prop, values, oem_no_mount)
619
620
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000621def ReadFromInputFile(input_file, fn):
622 """Reads the contents of fn from input zipfile or directory."""
623 if isinstance(input_file, zipfile.ZipFile):
624 return input_file.read(fn).decode()
625 else:
626 path = os.path.join(input_file, *fn.split("/"))
627 try:
628 with open(path) as f:
629 return f.read()
630 except IOError as e:
631 if e.errno == errno.ENOENT:
632 raise KeyError(fn)
633
634
Tao Bao410ad8b2018-08-24 12:08:38 -0700635def LoadInfoDict(input_file, repacking=False):
636 """Loads the key/value pairs from the given input target_files.
637
Tianjiea85bdf02020-07-29 11:56:19 -0700638 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700639 checks and returns the parsed key/value pairs for to the given build. It's
640 usually called early when working on input target_files files, e.g. when
641 generating OTAs, or signing builds. Note that the function may be called
642 against an old target_files file (i.e. from past dessert releases). So the
643 property parsing needs to be backward compatible.
644
645 In a `META/misc_info.txt`, a few properties are stored as links to the files
646 in the PRODUCT_OUT directory. It works fine with the build system. However,
647 they are no longer available when (re)generating images from target_files zip.
648 When `repacking` is True, redirect these properties to the actual files in the
649 unzipped directory.
650
651 Args:
652 input_file: The input target_files file, which could be an open
653 zipfile.ZipFile instance, or a str for the dir that contains the files
654 unzipped from a target_files file.
655 repacking: Whether it's trying repack an target_files file after loading the
656 info dict (default: False). If so, it will rewrite a few loaded
657 properties (e.g. selinux_fc, root_dir) to point to the actual files in
658 target_files file. When doing repacking, `input_file` must be a dir.
659
660 Returns:
661 A dict that contains the parsed key/value pairs.
662
663 Raises:
664 AssertionError: On invalid input arguments.
665 ValueError: On malformed input values.
666 """
667 if repacking:
668 assert isinstance(input_file, str), \
669 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700670
Doug Zongkerc9253822014-02-04 12:17:58 -0800671 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000672 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800673
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700674 try:
Michael Runge6e836112014-04-15 17:40:21 -0700675 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700676 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700677 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700678
Tao Bao410ad8b2018-08-24 12:08:38 -0700679 if "recovery_api_version" not in d:
680 raise ValueError("Failed to find 'recovery_api_version'")
681 if "fstab_version" not in d:
682 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800683
Tao Bao410ad8b2018-08-24 12:08:38 -0700684 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700685 # "selinux_fc" properties should point to the file_contexts files
686 # (file_contexts.bin) under META/.
687 for key in d:
688 if key.endswith("selinux_fc"):
689 fc_basename = os.path.basename(d[key])
690 fc_config = os.path.join(input_file, "META", fc_basename)
691 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700692
Daniel Norman72c626f2019-05-13 15:58:14 -0700693 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700694
Tom Cherryd14b8952018-08-09 14:26:00 -0700695 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700696 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700697 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700698 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700699
David Anderson0ec64ac2019-12-06 12:21:18 -0800700 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700701 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700702 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800703 key_name = part_name + "_base_fs_file"
704 if key_name not in d:
705 continue
706 basename = os.path.basename(d[key_name])
707 base_fs_file = os.path.join(input_file, "META", basename)
708 if os.path.exists(base_fs_file):
709 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700710 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700711 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800712 "Failed to find %s base fs file: %s", part_name, base_fs_file)
713 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700714
Doug Zongker37974732010-09-16 17:44:38 -0700715 def makeint(key):
716 if key in d:
717 d[key] = int(d[key], 0)
718
719 makeint("recovery_api_version")
720 makeint("blocksize")
721 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700722 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700723 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700724 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700725 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800726 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700727
Steve Muckle903a1ca2020-05-07 17:32:10 -0700728 boot_images = "boot.img"
729 if "boot_images" in d:
730 boot_images = d["boot_images"]
731 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400732 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700733
Tao Bao765668f2019-10-04 22:03:00 -0700734 # Load recovery fstab if applicable.
735 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800736
Tianjie Xu861f4132018-09-12 11:49:33 -0700737 # Tries to load the build props for all partitions with care_map, including
738 # system and vendor.
739 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800740 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000741 d[partition_prop] = PartitionBuildProps.FromInputFile(
742 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700743 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800744
Tao Bao3ed35d32019-10-07 20:48:48 -0700745 # Set up the salt (based on fingerprint) that will be used when adding AVB
746 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800747 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700748 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800749 for partition in PARTITIONS_WITH_CARE_MAP:
750 fingerprint = build_info.GetPartitionFingerprint(partition)
751 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400752 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400753 try:
754 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
755 except KeyError:
756 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700757 return d
758
Tao Baod1de6f32017-03-01 16:38:48 -0800759
Kelvin Zhang39aea442020-08-17 11:04:25 -0400760
Daniel Norman4cc9df62019-07-18 10:11:07 -0700761def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900762 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700763 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900764
Daniel Norman4cc9df62019-07-18 10:11:07 -0700765
766def LoadDictionaryFromFile(file_path):
767 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900768 return LoadDictionaryFromLines(lines)
769
770
Michael Runge6e836112014-04-15 17:40:21 -0700771def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700772 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700773 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700774 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700775 if not line or line.startswith("#"):
776 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700777 if "=" in line:
778 name, value = line.split("=", 1)
779 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700780 return d
781
Tao Baod1de6f32017-03-01 16:38:48 -0800782
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000783class PartitionBuildProps(object):
784 """The class holds the build prop of a particular partition.
785
786 This class loads the build.prop and holds the build properties for a given
787 partition. It also partially recognizes the 'import' statement in the
788 build.prop; and calculates alternative values of some specific build
789 properties during runtime.
790
791 Attributes:
792 input_file: a zipped target-file or an unzipped target-file directory.
793 partition: name of the partition.
794 props_allow_override: a list of build properties to search for the
795 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000796 build_props: a dict of build properties for the given partition.
797 prop_overrides: a set of props that are overridden by import.
798 placeholder_values: A dict of runtime variables' values to replace the
799 placeholders in the build.prop file. We expect exactly one value for
800 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000801 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400802
Tianjie Xu9afb2212020-05-10 21:48:15 +0000803 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000804 self.input_file = input_file
805 self.partition = name
806 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000807 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000808 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000809 self.prop_overrides = set()
810 self.placeholder_values = {}
811 if placeholder_values:
812 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000813
814 @staticmethod
815 def FromDictionary(name, build_props):
816 """Constructs an instance from a build prop dictionary."""
817
818 props = PartitionBuildProps("unknown", name)
819 props.build_props = build_props.copy()
820 return props
821
822 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000823 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000824 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000825 data = ''
826 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
827 '{}/build.prop'.format(name.upper())]:
828 try:
829 data = ReadFromInputFile(input_file, prop_file)
830 break
831 except KeyError:
832 logger.warning('Failed to read %s', prop_file)
833
Tianjie Xu9afb2212020-05-10 21:48:15 +0000834 props = PartitionBuildProps(input_file, name, placeholder_values)
835 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000836 return props
837
Yifan Hong125d0b62020-09-24 17:07:03 -0700838 @staticmethod
839 def FromBuildPropFile(name, build_prop_file):
840 """Constructs an instance from a build prop file."""
841
842 props = PartitionBuildProps("unknown", name)
843 with open(build_prop_file) as f:
844 props._LoadBuildProp(f.read())
845 return props
846
Tianjie Xu9afb2212020-05-10 21:48:15 +0000847 def _LoadBuildProp(self, data):
848 for line in data.split('\n'):
849 line = line.strip()
850 if not line or line.startswith("#"):
851 continue
852 if line.startswith("import"):
853 overrides = self._ImportParser(line)
854 duplicates = self.prop_overrides.intersection(overrides.keys())
855 if duplicates:
856 raise ValueError('prop {} is overridden multiple times'.format(
857 ','.join(duplicates)))
858 self.prop_overrides = self.prop_overrides.union(overrides.keys())
859 self.build_props.update(overrides)
860 elif "=" in line:
861 name, value = line.split("=", 1)
862 if name in self.prop_overrides:
863 raise ValueError('prop {} is set again after overridden by import '
864 'statement'.format(name))
865 self.build_props[name] = value
866
867 def _ImportParser(self, line):
868 """Parses the build prop in a given import statement."""
869
870 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400871 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000872 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700873
874 if len(tokens) == 3:
875 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
876 return {}
877
Tianjie Xu9afb2212020-05-10 21:48:15 +0000878 import_path = tokens[1]
879 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
880 raise ValueError('Unrecognized import path {}'.format(line))
881
882 # We only recognize a subset of import statement that the init process
883 # supports. And we can loose the restriction based on how the dynamic
884 # fingerprint is used in practice. The placeholder format should be
885 # ${placeholder}, and its value should be provided by the caller through
886 # the placeholder_values.
887 for prop, value in self.placeholder_values.items():
888 prop_place_holder = '${{{}}}'.format(prop)
889 if prop_place_holder in import_path:
890 import_path = import_path.replace(prop_place_holder, value)
891 if '$' in import_path:
892 logger.info('Unresolved place holder in import path %s', import_path)
893 return {}
894
895 import_path = import_path.replace('/{}'.format(self.partition),
896 self.partition.upper())
897 logger.info('Parsing build props override from %s', import_path)
898
899 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
900 d = LoadDictionaryFromLines(lines)
901 return {key: val for key, val in d.items()
902 if key in self.props_allow_override}
903
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000904 def GetProp(self, prop):
905 return self.build_props.get(prop)
906
907
Tianjie Xucfa86222016-03-07 16:31:19 -0800908def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
909 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700910 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700911 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700912 self.mount_point = mount_point
913 self.fs_type = fs_type
914 self.device = device
915 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700916 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700917 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700918
919 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800920 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700921 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700922 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700923 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700924
Tao Baod1de6f32017-03-01 16:38:48 -0800925 assert fstab_version == 2
926
927 d = {}
928 for line in data.split("\n"):
929 line = line.strip()
930 if not line or line.startswith("#"):
931 continue
932
933 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
934 pieces = line.split()
935 if len(pieces) != 5:
936 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
937
938 # Ignore entries that are managed by vold.
939 options = pieces[4]
940 if "voldmanaged=" in options:
941 continue
942
943 # It's a good line, parse it.
944 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700945 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800946 options = options.split(",")
947 for i in options:
948 if i.startswith("length="):
949 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700950 elif i == "slotselect":
951 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800952 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800953 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700954 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800955
Tao Baod1de6f32017-03-01 16:38:48 -0800956 mount_flags = pieces[3]
957 # Honor the SELinux context if present.
958 context = None
959 for i in mount_flags.split(","):
960 if i.startswith("context="):
961 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800962
Tao Baod1de6f32017-03-01 16:38:48 -0800963 mount_point = pieces[1]
964 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700965 device=pieces[0], length=length, context=context,
966 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800967
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700968 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700969 # system. Other areas assume system is always at "/system" so point /system
970 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700971 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800972 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700973 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700974 return d
975
976
Tao Bao765668f2019-10-04 22:03:00 -0700977def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
978 """Finds the path to recovery fstab and loads its contents."""
979 # recovery fstab is only meaningful when installing an update via recovery
980 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700981 if info_dict.get('ab_update') == 'true' and \
982 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700983 return None
984
985 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
986 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
987 # cases, since it may load the info_dict from an old build (e.g. when
988 # generating incremental OTAs from that build).
989 system_root_image = info_dict.get('system_root_image') == 'true'
990 if info_dict.get('no_recovery') != 'true':
991 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
992 if isinstance(input_file, zipfile.ZipFile):
993 if recovery_fstab_path not in input_file.namelist():
994 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
995 else:
996 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
997 if not os.path.exists(path):
998 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
999 return LoadRecoveryFSTab(
1000 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1001 system_root_image)
1002
1003 if info_dict.get('recovery_as_boot') == 'true':
1004 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1005 if isinstance(input_file, zipfile.ZipFile):
1006 if recovery_fstab_path not in input_file.namelist():
1007 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1008 else:
1009 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1010 if not os.path.exists(path):
1011 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1012 return LoadRecoveryFSTab(
1013 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1014 system_root_image)
1015
1016 return None
1017
1018
Doug Zongker37974732010-09-16 17:44:38 -07001019def DumpInfoDict(d):
1020 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001021 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001022
Dan Albert8b72aef2015-03-23 19:13:21 -07001023
Daniel Norman55417142019-11-25 16:04:36 -08001024def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001025 """Merges dynamic partition info variables.
1026
1027 Args:
1028 framework_dict: The dictionary of dynamic partition info variables from the
1029 partial framework target files.
1030 vendor_dict: The dictionary of dynamic partition info variables from the
1031 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001032
1033 Returns:
1034 The merged dynamic partition info dictionary.
1035 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001036
1037 def uniq_concat(a, b):
1038 combined = set(a.split(" "))
1039 combined.update(set(b.split(" ")))
1040 combined = [item.strip() for item in combined if item.strip()]
1041 return " ".join(sorted(combined))
1042
1043 if (framework_dict.get("use_dynamic_partitions") !=
1044 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1045 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1046
1047 merged_dict = {"use_dynamic_partitions": "true"}
1048
1049 merged_dict["dynamic_partition_list"] = uniq_concat(
1050 framework_dict.get("dynamic_partition_list", ""),
1051 vendor_dict.get("dynamic_partition_list", ""))
1052
1053 # Super block devices are defined by the vendor dict.
1054 if "super_block_devices" in vendor_dict:
1055 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1056 for block_device in merged_dict["super_block_devices"].split(" "):
1057 key = "super_%s_device_size" % block_device
1058 if key not in vendor_dict:
1059 raise ValueError("Vendor dict does not contain required key %s." % key)
1060 merged_dict[key] = vendor_dict[key]
1061
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001062 # Partition groups and group sizes are defined by the vendor dict because
1063 # these values may vary for each board that uses a shared system image.
1064 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001065 for partition_group in merged_dict["super_partition_groups"].split(" "):
1066 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001067 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001068 if key not in vendor_dict:
1069 raise ValueError("Vendor dict does not contain required key %s." % key)
1070 merged_dict[key] = vendor_dict[key]
1071
1072 # Set the partition group's partition list using a concatenation of the
1073 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001074 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001075 merged_dict[key] = uniq_concat(
1076 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301077
Daniel Normanb0c75912020-09-24 14:30:21 -07001078 # Various other flags should be copied from the vendor dict, if defined.
1079 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1080 "super_metadata_device", "super_partition_error_limit",
1081 "super_partition_size"):
1082 if key in vendor_dict.keys():
1083 merged_dict[key] = vendor_dict[key]
1084
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 return merged_dict
1086
1087
Daniel Normand3351562020-10-29 12:33:11 -07001088def SharedUidPartitionViolations(uid_dict, partition_groups):
1089 """Checks for APK sharedUserIds that cross partition group boundaries.
1090
1091 This uses a single or merged build's shareduid_violation_modules.json
1092 output file, as generated by find_shareduid_violation.py or
1093 core/tasks/find-shareduid-violation.mk.
1094
1095 An error is defined as a sharedUserId that is found in a set of partitions
1096 that span more than one partition group.
1097
1098 Args:
1099 uid_dict: A dictionary created by using the standard json module to read a
1100 complete shareduid_violation_modules.json file.
1101 partition_groups: A list of groups, where each group is a list of
1102 partitions.
1103
1104 Returns:
1105 A list of error messages.
1106 """
1107 errors = []
1108 for uid, partitions in uid_dict.items():
1109 found_in_groups = [
1110 group for group in partition_groups
1111 if set(partitions.keys()) & set(group)
1112 ]
1113 if len(found_in_groups) > 1:
1114 errors.append(
1115 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1116 % (uid, ",".join(sorted(partitions.keys()))))
1117 return errors
1118
1119
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001120def AppendAVBSigningArgs(cmd, partition):
1121 """Append signing arguments for avbtool."""
1122 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1123 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001124 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1125 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1126 if os.path.exists(new_key_path):
1127 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001128 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1129 if key_path and algorithm:
1130 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001131 avb_salt = OPTIONS.info_dict.get("avb_salt")
1132 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001133 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001134 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001135
1136
Tao Bao765668f2019-10-04 22:03:00 -07001137def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001138 """Returns the VBMeta arguments for partition.
1139
1140 It sets up the VBMeta argument by including the partition descriptor from the
1141 given 'image', or by configuring the partition as a chained partition.
1142
1143 Args:
1144 partition: The name of the partition (e.g. "system").
1145 image: The path to the partition image.
1146 info_dict: A dict returned by common.LoadInfoDict(). Will use
1147 OPTIONS.info_dict if None has been given.
1148
1149 Returns:
1150 A list of VBMeta arguments.
1151 """
1152 if info_dict is None:
1153 info_dict = OPTIONS.info_dict
1154
1155 # Check if chain partition is used.
1156 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001157 if not key_path:
1158 return ["--include_descriptors_from_image", image]
1159
1160 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1161 # into vbmeta.img. The recovery image will be configured on an independent
1162 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1163 # See details at
1164 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001165 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001166 return []
1167
1168 # Otherwise chain the partition into vbmeta.
1169 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1170 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001171
1172
Tao Bao02a08592018-07-22 12:40:45 -07001173def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1174 """Constructs and returns the arg to build or verify a chained partition.
1175
1176 Args:
1177 partition: The partition name.
1178 info_dict: The info dict to look up the key info and rollback index
1179 location.
1180 key: The key to be used for building or verifying the partition. Defaults to
1181 the key listed in info_dict.
1182
1183 Returns:
1184 A string of form "partition:rollback_index_location:key" that can be used to
1185 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001186 """
1187 if key is None:
1188 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001189 if key and not os.path.exists(key) and OPTIONS.search_path:
1190 new_key_path = os.path.join(OPTIONS.search_path, key)
1191 if os.path.exists(new_key_path):
1192 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001193 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001194 rollback_index_location = info_dict[
1195 "avb_" + partition + "_rollback_index_location"]
1196 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1197
1198
Tianjie20dd8f22020-04-19 15:51:16 -07001199def ConstructAftlMakeImageCommands(output_image):
1200 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001201
1202 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001203 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001204 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1205 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1206 'No AFTL manufacturer key provided.'
1207
1208 vbmeta_image = MakeTempFile()
1209 os.rename(output_image, vbmeta_image)
1210 build_info = BuildInfo(OPTIONS.info_dict)
1211 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001212 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001213 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001214 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001215 "--vbmeta_image_path", vbmeta_image,
1216 "--output", output_image,
1217 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001218 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001219 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1220 "--algorithm", "SHA256_RSA4096",
1221 "--padding", "4096"]
1222 if OPTIONS.aftl_signer_helper:
1223 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001224 return aftl_cmd
1225
1226
1227def AddAftlInclusionProof(output_image):
1228 """Appends the aftl inclusion proof to the vbmeta image."""
1229
1230 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001231 RunAndCheckOutput(aftl_cmd)
1232
1233 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1234 output_image, '--transparency_log_pub_keys',
1235 OPTIONS.aftl_key_path]
1236 RunAndCheckOutput(verify_cmd)
1237
1238
Daniel Norman276f0622019-07-26 14:13:51 -07001239def BuildVBMeta(image_path, partitions, name, needed_partitions):
1240 """Creates a VBMeta image.
1241
1242 It generates the requested VBMeta image. The requested image could be for
1243 top-level or chained VBMeta image, which is determined based on the name.
1244
1245 Args:
1246 image_path: The output path for the new VBMeta image.
1247 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001248 values. Only valid partition names are accepted, as partitions listed
1249 in common.AVB_PARTITIONS and custom partitions listed in
1250 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001251 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1252 needed_partitions: Partitions whose descriptors should be included into the
1253 generated VBMeta image.
1254
1255 Raises:
1256 AssertionError: On invalid input args.
1257 """
1258 avbtool = OPTIONS.info_dict["avb_avbtool"]
1259 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1260 AppendAVBSigningArgs(cmd, name)
1261
Hongguang Chenf23364d2020-04-27 18:36:36 -07001262 custom_partitions = OPTIONS.info_dict.get(
1263 "avb_custom_images_partition_list", "").strip().split()
1264
Daniel Norman276f0622019-07-26 14:13:51 -07001265 for partition, path in partitions.items():
1266 if partition not in needed_partitions:
1267 continue
1268 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001269 partition in AVB_VBMETA_PARTITIONS or
1270 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001271 'Unknown partition: {}'.format(partition)
1272 assert os.path.exists(path), \
1273 'Failed to find {} for {}'.format(path, partition)
1274 cmd.extend(GetAvbPartitionArg(partition, path))
1275
1276 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1277 if args and args.strip():
1278 split_args = shlex.split(args)
1279 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001280 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001281 # as a path relative to source tree, which may not be available at the
1282 # same location when running this script (we have the input target_files
1283 # zip only). For such cases, we additionally scan other locations (e.g.
1284 # IMAGES/, RADIO/, etc) before bailing out.
1285 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001286 chained_image = split_args[index + 1]
1287 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001288 continue
1289 found = False
1290 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1291 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001292 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001293 if os.path.exists(alt_path):
1294 split_args[index + 1] = alt_path
1295 found = True
1296 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001297 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001298 cmd.extend(split_args)
1299
1300 RunAndCheckOutput(cmd)
1301
Tianjie Xueaed60c2020-03-12 00:33:28 -07001302 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001303 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001304 AddAftlInclusionProof(image_path)
1305
Daniel Norman276f0622019-07-26 14:13:51 -07001306
J. Avila98cd4cc2020-06-10 20:09:10 +00001307def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001308 ramdisk_img = tempfile.NamedTemporaryFile()
1309
1310 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1311 cmd = ["mkbootfs", "-f", fs_config_file,
1312 os.path.join(sourcedir, "RAMDISK")]
1313 else:
1314 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1315 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001316 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001317 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001318 stdout=ramdisk_img.file.fileno())
1319 else:
1320 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001321
1322 p2.wait()
1323 p1.wait()
1324 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001325 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001326
1327 return ramdisk_img
1328
1329
Steve Muckle9793cf62020-04-08 18:27:00 -07001330def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001331 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001332 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001333
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001334 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001335 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1336 we are building a two-step special image (i.e. building a recovery image to
1337 be loaded into /boot in two-step OTAs).
1338
1339 Return the image data, or None if sourcedir does not appear to contains files
1340 for building the requested image.
1341 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001342
Yifan Hong63c5ca12020-10-08 11:54:02 -07001343 if info_dict is None:
1344 info_dict = OPTIONS.info_dict
1345
Steve Muckle9793cf62020-04-08 18:27:00 -07001346 # "boot" or "recovery", without extension.
1347 partition_name = os.path.basename(sourcedir).lower()
1348
Yifan Hong63c5ca12020-10-08 11:54:02 -07001349 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001350 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001351 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1352 logger.info("Excluded kernel binary from recovery image.")
1353 else:
1354 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001355 else:
1356 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001357 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001358 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001359 return None
1360
1361 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001362 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001363
Doug Zongkereef39442009-04-02 12:14:19 -07001364 img = tempfile.NamedTemporaryFile()
1365
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001366 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001367 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1368 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001369
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001370 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1371 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1372
Yifan Hong63c5ca12020-10-08 11:54:02 -07001373 cmd = [mkbootimg]
1374 if kernel:
1375 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001376
Benoit Fradina45a8682014-07-14 21:00:43 +02001377 fn = os.path.join(sourcedir, "second")
1378 if os.access(fn, os.F_OK):
1379 cmd.append("--second")
1380 cmd.append(fn)
1381
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001382 fn = os.path.join(sourcedir, "dtb")
1383 if os.access(fn, os.F_OK):
1384 cmd.append("--dtb")
1385 cmd.append(fn)
1386
Doug Zongker171f1cd2009-06-15 22:36:37 -07001387 fn = os.path.join(sourcedir, "cmdline")
1388 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001389 cmd.append("--cmdline")
1390 cmd.append(open(fn).read().rstrip("\n"))
1391
1392 fn = os.path.join(sourcedir, "base")
1393 if os.access(fn, os.F_OK):
1394 cmd.append("--base")
1395 cmd.append(open(fn).read().rstrip("\n"))
1396
Ying Wang4de6b5b2010-08-25 14:29:34 -07001397 fn = os.path.join(sourcedir, "pagesize")
1398 if os.access(fn, os.F_OK):
1399 cmd.append("--pagesize")
1400 cmd.append(open(fn).read().rstrip("\n"))
1401
Steve Mucklef84668e2020-03-16 19:13:46 -07001402 if partition_name == "recovery":
1403 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301404 if not args:
1405 # Fall back to "mkbootimg_args" for recovery image
1406 # in case "recovery_mkbootimg_args" is not set.
1407 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001408 else:
1409 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001410 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001411 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001412
Tao Bao76def242017-11-21 09:25:31 -08001413 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001414 if args and args.strip():
1415 cmd.extend(shlex.split(args))
1416
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001417 if has_ramdisk:
1418 cmd.extend(["--ramdisk", ramdisk_img.name])
1419
Tao Baod95e9fd2015-03-29 23:07:41 -07001420 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001421 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001422 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001423 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001424 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001425 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001426
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001427 if partition_name == "recovery":
1428 if info_dict.get("include_recovery_dtbo") == "true":
1429 fn = os.path.join(sourcedir, "recovery_dtbo")
1430 cmd.extend(["--recovery_dtbo", fn])
1431 if info_dict.get("include_recovery_acpio") == "true":
1432 fn = os.path.join(sourcedir, "recovery_acpio")
1433 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001434
Tao Bao986ee862018-10-04 15:46:16 -07001435 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001436
Tao Bao76def242017-11-21 09:25:31 -08001437 if (info_dict.get("boot_signer") == "true" and
1438 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001439 # Hard-code the path as "/boot" for two-step special recovery image (which
1440 # will be loaded into /boot during the two-step OTA).
1441 if two_step_image:
1442 path = "/boot"
1443 else:
Tao Baobf70c312017-07-11 17:27:55 -07001444 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001445 cmd = [OPTIONS.boot_signer_path]
1446 cmd.extend(OPTIONS.boot_signer_args)
1447 cmd.extend([path, img.name,
1448 info_dict["verity_key"] + ".pk8",
1449 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001450 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001451
Tao Baod95e9fd2015-03-29 23:07:41 -07001452 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001453 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001454 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001455 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001456 # We have switched from the prebuilt futility binary to using the tool
1457 # (futility-host) built from the source. Override the setting in the old
1458 # TF.zip.
1459 futility = info_dict["futility"]
1460 if futility.startswith("prebuilts/"):
1461 futility = "futility-host"
1462 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001463 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001464 info_dict["vboot_key"] + ".vbprivk",
1465 info_dict["vboot_subkey"] + ".vbprivk",
1466 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001467 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001468 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001469
Tao Baof3282b42015-04-01 11:21:55 -07001470 # Clean up the temp files.
1471 img_unsigned.close()
1472 img_keyblock.close()
1473
David Zeuthen8fecb282017-12-01 16:24:01 -05001474 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001475 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001476 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001477 if partition_name == "recovery":
1478 part_size = info_dict["recovery_size"]
1479 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001480 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001481 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001482 "--partition_size", str(part_size), "--partition_name",
1483 partition_name]
1484 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001485 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001486 if args and args.strip():
1487 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001488 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001489
1490 img.seek(os.SEEK_SET, 0)
1491 data = img.read()
1492
1493 if has_ramdisk:
1494 ramdisk_img.close()
1495 img.close()
1496
1497 return data
1498
1499
Doug Zongkerd5131602012-08-02 14:46:42 -07001500def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001501 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001502 """Return a File object with the desired bootable image.
1503
1504 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1505 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1506 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001507
Doug Zongker55d93282011-01-25 17:03:34 -08001508 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1509 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001510 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001511 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001512
1513 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1514 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001515 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001516 return File.FromLocalFile(name, prebuilt_path)
1517
Tao Bao32fcdab2018-10-12 10:30:39 -07001518 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001519
1520 if info_dict is None:
1521 info_dict = OPTIONS.info_dict
1522
1523 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001524 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1525 # for recovery.
1526 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1527 prebuilt_name != "boot.img" or
1528 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001529
Doug Zongker6f1d0312014-08-22 08:07:12 -07001530 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001531 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001532 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001533 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001534 if data:
1535 return File(name, data)
1536 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001537
Doug Zongkereef39442009-04-02 12:14:19 -07001538
Steve Mucklee1b10862019-07-10 10:49:37 -07001539def _BuildVendorBootImage(sourcedir, info_dict=None):
1540 """Build a vendor boot image from the specified sourcedir.
1541
1542 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1543 turn them into a vendor boot image.
1544
1545 Return the image data, or None if sourcedir does not appear to contains files
1546 for building the requested image.
1547 """
1548
1549 if info_dict is None:
1550 info_dict = OPTIONS.info_dict
1551
1552 img = tempfile.NamedTemporaryFile()
1553
J. Avila98cd4cc2020-06-10 20:09:10 +00001554 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1555 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001556
1557 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1558 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1559
1560 cmd = [mkbootimg]
1561
1562 fn = os.path.join(sourcedir, "dtb")
1563 if os.access(fn, os.F_OK):
1564 cmd.append("--dtb")
1565 cmd.append(fn)
1566
1567 fn = os.path.join(sourcedir, "vendor_cmdline")
1568 if os.access(fn, os.F_OK):
1569 cmd.append("--vendor_cmdline")
1570 cmd.append(open(fn).read().rstrip("\n"))
1571
1572 fn = os.path.join(sourcedir, "base")
1573 if os.access(fn, os.F_OK):
1574 cmd.append("--base")
1575 cmd.append(open(fn).read().rstrip("\n"))
1576
1577 fn = os.path.join(sourcedir, "pagesize")
1578 if os.access(fn, os.F_OK):
1579 cmd.append("--pagesize")
1580 cmd.append(open(fn).read().rstrip("\n"))
1581
1582 args = info_dict.get("mkbootimg_args")
1583 if args and args.strip():
1584 cmd.extend(shlex.split(args))
1585
1586 args = info_dict.get("mkbootimg_version_args")
1587 if args and args.strip():
1588 cmd.extend(shlex.split(args))
1589
1590 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1591 cmd.extend(["--vendor_boot", img.name])
1592
1593 RunAndCheckOutput(cmd)
1594
1595 # AVB: if enabled, calculate and add hash.
1596 if info_dict.get("avb_enable") == "true":
1597 avbtool = info_dict["avb_avbtool"]
1598 part_size = info_dict["vendor_boot_size"]
1599 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001600 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001601 AppendAVBSigningArgs(cmd, "vendor_boot")
1602 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1603 if args and args.strip():
1604 cmd.extend(shlex.split(args))
1605 RunAndCheckOutput(cmd)
1606
1607 img.seek(os.SEEK_SET, 0)
1608 data = img.read()
1609
1610 ramdisk_img.close()
1611 img.close()
1612
1613 return data
1614
1615
1616def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1617 info_dict=None):
1618 """Return a File object with the desired vendor boot image.
1619
1620 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1621 the source files in 'unpack_dir'/'tree_subdir'."""
1622
1623 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1624 if os.path.exists(prebuilt_path):
1625 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1626 return File.FromLocalFile(name, prebuilt_path)
1627
1628 logger.info("building image from target_files %s...", tree_subdir)
1629
1630 if info_dict is None:
1631 info_dict = OPTIONS.info_dict
1632
Kelvin Zhang0876c412020-06-23 15:06:58 -04001633 data = _BuildVendorBootImage(
1634 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001635 if data:
1636 return File(name, data)
1637 return None
1638
1639
Narayan Kamatha07bf042017-08-14 14:49:21 +01001640def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001641 """Gunzips the given gzip compressed file to a given output file."""
1642 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001643 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001644 shutil.copyfileobj(in_file, out_file)
1645
1646
Tao Bao0ff15de2019-03-20 11:26:06 -07001647def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001648 """Unzips the archive to the given directory.
1649
1650 Args:
1651 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001652 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001653 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1654 archvie. Non-matching patterns will be filtered out. If there's no match
1655 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001656 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001657 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001658 if patterns is not None:
1659 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001660 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001661 names = input_zip.namelist()
1662 filtered = [
1663 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1664
1665 # There isn't any matching files. Don't unzip anything.
1666 if not filtered:
1667 return
1668 cmd.extend(filtered)
1669
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001670 RunAndCheckOutput(cmd)
1671
1672
Doug Zongker75f17362009-12-08 13:46:44 -08001673def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001674 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001675
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001676 Args:
1677 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1678 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1679
1680 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1681 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001682
Tao Bao1c830bf2017-12-25 10:43:47 -08001683 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001684 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001685 """
Doug Zongkereef39442009-04-02 12:14:19 -07001686
Tao Bao1c830bf2017-12-25 10:43:47 -08001687 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001688 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1689 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001690 UnzipToDir(m.group(1), tmp, pattern)
1691 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001692 filename = m.group(1)
1693 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001694 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001695
Tao Baodba59ee2018-01-09 13:21:02 -08001696 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001697
1698
Yifan Hong8a66a712019-04-04 15:37:57 -07001699def GetUserImage(which, tmpdir, input_zip,
1700 info_dict=None,
1701 allow_shared_blocks=None,
1702 hashtree_info_generator=None,
1703 reset_file_map=False):
1704 """Returns an Image object suitable for passing to BlockImageDiff.
1705
1706 This function loads the specified image from the given path. If the specified
1707 image is sparse, it also performs additional processing for OTA purpose. For
1708 example, it always adds block 0 to clobbered blocks list. It also detects
1709 files that cannot be reconstructed from the block list, for whom we should
1710 avoid applying imgdiff.
1711
1712 Args:
1713 which: The partition name.
1714 tmpdir: The directory that contains the prebuilt image and block map file.
1715 input_zip: The target-files ZIP archive.
1716 info_dict: The dict to be looked up for relevant info.
1717 allow_shared_blocks: If image is sparse, whether having shared blocks is
1718 allowed. If none, it is looked up from info_dict.
1719 hashtree_info_generator: If present and image is sparse, generates the
1720 hashtree_info for this sparse image.
1721 reset_file_map: If true and image is sparse, reset file map before returning
1722 the image.
1723 Returns:
1724 A Image object. If it is a sparse image and reset_file_map is False, the
1725 image will have file_map info loaded.
1726 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001727 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001728 info_dict = LoadInfoDict(input_zip)
1729
1730 is_sparse = info_dict.get("extfs_sparse_flag")
1731
1732 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1733 # shared blocks (i.e. some blocks will show up in multiple files' block
1734 # list). We can only allocate such shared blocks to the first "owner", and
1735 # disable imgdiff for all later occurrences.
1736 if allow_shared_blocks is None:
1737 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1738
1739 if is_sparse:
1740 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1741 hashtree_info_generator)
1742 if reset_file_map:
1743 img.ResetFileMap()
1744 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001745 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001746
1747
1748def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1749 """Returns a Image object suitable for passing to BlockImageDiff.
1750
1751 This function loads the specified non-sparse image from the given path.
1752
1753 Args:
1754 which: The partition name.
1755 tmpdir: The directory that contains the prebuilt image and block map file.
1756 Returns:
1757 A Image object.
1758 """
1759 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1760 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1761
1762 # The image and map files must have been created prior to calling
1763 # ota_from_target_files.py (since LMP).
1764 assert os.path.exists(path) and os.path.exists(mappath)
1765
Tianjie Xu41976c72019-07-03 13:57:01 -07001766 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1767
Yifan Hong8a66a712019-04-04 15:37:57 -07001768
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001769def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1770 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001771 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1772
1773 This function loads the specified sparse image from the given path, and
1774 performs additional processing for OTA purpose. For example, it always adds
1775 block 0 to clobbered blocks list. It also detects files that cannot be
1776 reconstructed from the block list, for whom we should avoid applying imgdiff.
1777
1778 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001779 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001780 tmpdir: The directory that contains the prebuilt image and block map file.
1781 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001782 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001783 hashtree_info_generator: If present, generates the hashtree_info for this
1784 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001785 Returns:
1786 A SparseImage object, with file_map info loaded.
1787 """
Tao Baoc765cca2018-01-31 17:32:40 -08001788 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1789 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1790
1791 # The image and map files must have been created prior to calling
1792 # ota_from_target_files.py (since LMP).
1793 assert os.path.exists(path) and os.path.exists(mappath)
1794
1795 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1796 # it to clobbered_blocks so that it will be written to the target
1797 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1798 clobbered_blocks = "0"
1799
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001800 image = sparse_img.SparseImage(
1801 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1802 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001803
1804 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1805 # if they contain all zeros. We can't reconstruct such a file from its block
1806 # list. Tag such entries accordingly. (Bug: 65213616)
1807 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001808 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001809 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001810 continue
1811
Tom Cherryd14b8952018-08-09 14:26:00 -07001812 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1813 # filename listed in system.map may contain an additional leading slash
1814 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1815 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001816 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001817
Tom Cherryd14b8952018-08-09 14:26:00 -07001818 # Special handling another case, where files not under /system
1819 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001820 if which == 'system' and not arcname.startswith('SYSTEM'):
1821 arcname = 'ROOT/' + arcname
1822
1823 assert arcname in input_zip.namelist(), \
1824 "Failed to find the ZIP entry for {}".format(entry)
1825
Tao Baoc765cca2018-01-31 17:32:40 -08001826 info = input_zip.getinfo(arcname)
1827 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001828
1829 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001830 # image, check the original block list to determine its completeness. Note
1831 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001832 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001833 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001834
Tao Baoc765cca2018-01-31 17:32:40 -08001835 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1836 ranges.extra['incomplete'] = True
1837
1838 return image
1839
1840
Doug Zongkereef39442009-04-02 12:14:19 -07001841def GetKeyPasswords(keylist):
1842 """Given a list of keys, prompt the user to enter passwords for
1843 those which require them. Return a {key: password} dict. password
1844 will be None if the key has no password."""
1845
Doug Zongker8ce7c252009-05-22 13:34:54 -07001846 no_passwords = []
1847 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001848 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001849 devnull = open("/dev/null", "w+b")
1850 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001851 # We don't need a password for things that aren't really keys.
1852 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001853 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001854 continue
1855
T.R. Fullhart37e10522013-03-18 10:31:26 -07001856 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001857 "-inform", "DER", "-nocrypt"],
1858 stdin=devnull.fileno(),
1859 stdout=devnull.fileno(),
1860 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001861 p.communicate()
1862 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001863 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001864 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001865 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001866 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1867 "-inform", "DER", "-passin", "pass:"],
1868 stdin=devnull.fileno(),
1869 stdout=devnull.fileno(),
1870 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001871 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001872 if p.returncode == 0:
1873 # Encrypted key with empty string as password.
1874 key_passwords[k] = ''
1875 elif stderr.startswith('Error decrypting key'):
1876 # Definitely encrypted key.
1877 # It would have said "Error reading key" if it didn't parse correctly.
1878 need_passwords.append(k)
1879 else:
1880 # Potentially, a type of key that openssl doesn't understand.
1881 # We'll let the routines in signapk.jar handle it.
1882 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001883 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001884
T.R. Fullhart37e10522013-03-18 10:31:26 -07001885 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001886 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001887 return key_passwords
1888
1889
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001890def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001891 """Gets the minSdkVersion declared in the APK.
1892
changho.shin0f125362019-07-08 10:59:00 +09001893 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001894 This can be both a decimal number (API Level) or a codename.
1895
1896 Args:
1897 apk_name: The APK filename.
1898
1899 Returns:
1900 The parsed SDK version string.
1901
1902 Raises:
1903 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001904 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001905 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001906 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001907 stderr=subprocess.PIPE)
1908 stdoutdata, stderrdata = proc.communicate()
1909 if proc.returncode != 0:
1910 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001911 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001912 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001913
Tao Baof47bf0f2018-03-21 23:28:51 -07001914 for line in stdoutdata.split("\n"):
1915 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001916 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1917 if m:
1918 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001919 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001920
1921
1922def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001923 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001924
Tao Baof47bf0f2018-03-21 23:28:51 -07001925 If minSdkVersion is set to a codename, it is translated to a number using the
1926 provided map.
1927
1928 Args:
1929 apk_name: The APK filename.
1930
1931 Returns:
1932 The parsed SDK version number.
1933
1934 Raises:
1935 ExternalError: On failing to get the min SDK version number.
1936 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001937 version = GetMinSdkVersion(apk_name)
1938 try:
1939 return int(version)
1940 except ValueError:
1941 # Not a decimal number. Codename?
1942 if version in codename_to_api_level_map:
1943 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001944 raise ExternalError(
1945 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1946 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001947
1948
1949def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001950 codename_to_api_level_map=None, whole_file=False,
1951 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001952 """Sign the input_name zip/jar/apk, producing output_name. Use the
1953 given key and password (the latter may be None if the key does not
1954 have a password.
1955
Doug Zongker951495f2009-08-14 12:44:19 -07001956 If whole_file is true, use the "-w" option to SignApk to embed a
1957 signature that covers the whole file in the archive comment of the
1958 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001959
1960 min_api_level is the API Level (int) of the oldest platform this file may end
1961 up on. If not specified for an APK, the API Level is obtained by interpreting
1962 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1963
1964 codename_to_api_level_map is needed to translate the codename which may be
1965 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001966
1967 Caller may optionally specify extra args to be passed to SignApk, which
1968 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001969 """
Tao Bao76def242017-11-21 09:25:31 -08001970 if codename_to_api_level_map is None:
1971 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001972 if extra_signapk_args is None:
1973 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001974
Alex Klyubin9667b182015-12-10 13:38:50 -08001975 java_library_path = os.path.join(
1976 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1977
Tao Baoe95540e2016-11-08 12:08:53 -08001978 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1979 ["-Djava.library.path=" + java_library_path,
1980 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001981 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001982 if whole_file:
1983 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001984
1985 min_sdk_version = min_api_level
1986 if min_sdk_version is None:
1987 if not whole_file:
1988 min_sdk_version = GetMinSdkVersionInt(
1989 input_name, codename_to_api_level_map)
1990 if min_sdk_version is not None:
1991 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1992
T.R. Fullhart37e10522013-03-18 10:31:26 -07001993 cmd.extend([key + OPTIONS.public_key_suffix,
1994 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001995 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001996
Tao Bao73dd4f42018-10-04 16:25:33 -07001997 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001998 if password is not None:
1999 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002000 stdoutdata, _ = proc.communicate(password)
2001 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002002 raise ExternalError(
2003 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002004 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002005
Doug Zongkereef39442009-04-02 12:14:19 -07002006
Doug Zongker37974732010-09-16 17:44:38 -07002007def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002008 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002009
Tao Bao9dd909e2017-11-14 11:27:32 -08002010 For non-AVB images, raise exception if the data is too big. Print a warning
2011 if the data is nearing the maximum size.
2012
2013 For AVB images, the actual image size should be identical to the limit.
2014
2015 Args:
2016 data: A string that contains all the data for the partition.
2017 target: The partition name. The ".img" suffix is optional.
2018 info_dict: The dict to be looked up for relevant info.
2019 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002020 if target.endswith(".img"):
2021 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002022 mount_point = "/" + target
2023
Ying Wangf8824af2014-06-03 14:07:27 -07002024 fs_type = None
2025 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002026 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002027 if mount_point == "/userdata":
2028 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002029 p = info_dict["fstab"][mount_point]
2030 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002031 device = p.device
2032 if "/" in device:
2033 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002034 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002035 if not fs_type or not limit:
2036 return
Doug Zongkereef39442009-04-02 12:14:19 -07002037
Andrew Boie0f9aec82012-02-14 09:32:52 -08002038 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002039 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2040 # path.
2041 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2042 if size != limit:
2043 raise ExternalError(
2044 "Mismatching image size for %s: expected %d actual %d" % (
2045 target, limit, size))
2046 else:
2047 pct = float(size) * 100.0 / limit
2048 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2049 if pct >= 99.0:
2050 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002051
2052 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002053 logger.warning("\n WARNING: %s\n", msg)
2054 else:
2055 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002056
2057
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002058def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002059 """Parses the APK certs info from a given target-files zip.
2060
2061 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2062 tuple with the following elements: (1) a dictionary that maps packages to
2063 certs (based on the "certificate" and "private_key" attributes in the file;
2064 (2) a string representing the extension of compressed APKs in the target files
2065 (e.g ".gz", ".bro").
2066
2067 Args:
2068 tf_zip: The input target_files ZipFile (already open).
2069
2070 Returns:
2071 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2072 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2073 no compressed APKs.
2074 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002075 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002076 compressed_extension = None
2077
Tao Bao0f990332017-09-08 19:02:54 -07002078 # META/apkcerts.txt contains the info for _all_ the packages known at build
2079 # time. Filter out the ones that are not installed.
2080 installed_files = set()
2081 for name in tf_zip.namelist():
2082 basename = os.path.basename(name)
2083 if basename:
2084 installed_files.add(basename)
2085
Tao Baoda30cfa2017-12-01 16:19:46 -08002086 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002087 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002088 if not line:
2089 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002090 m = re.match(
2091 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002092 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2093 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002094 line)
2095 if not m:
2096 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002097
Tao Bao818ddf52018-01-05 11:17:34 -08002098 matches = m.groupdict()
2099 cert = matches["CERT"]
2100 privkey = matches["PRIVKEY"]
2101 name = matches["NAME"]
2102 this_compressed_extension = matches["COMPRESSED"]
2103
2104 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2105 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2106 if cert in SPECIAL_CERT_STRINGS and not privkey:
2107 certmap[name] = cert
2108 elif (cert.endswith(OPTIONS.public_key_suffix) and
2109 privkey.endswith(OPTIONS.private_key_suffix) and
2110 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2111 certmap[name] = cert[:-public_key_suffix_len]
2112 else:
2113 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2114
2115 if not this_compressed_extension:
2116 continue
2117
2118 # Only count the installed files.
2119 filename = name + '.' + this_compressed_extension
2120 if filename not in installed_files:
2121 continue
2122
2123 # Make sure that all the values in the compression map have the same
2124 # extension. We don't support multiple compression methods in the same
2125 # system image.
2126 if compressed_extension:
2127 if this_compressed_extension != compressed_extension:
2128 raise ValueError(
2129 "Multiple compressed extensions: {} vs {}".format(
2130 compressed_extension, this_compressed_extension))
2131 else:
2132 compressed_extension = this_compressed_extension
2133
2134 return (certmap,
2135 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002136
2137
Doug Zongkereef39442009-04-02 12:14:19 -07002138COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002139Global options
2140
2141 -p (--path) <dir>
2142 Prepend <dir>/bin to the list of places to search for binaries run by this
2143 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002144
Doug Zongker05d3dea2009-06-22 11:32:31 -07002145 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002146 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002147
Tao Bao30df8b42018-04-23 15:32:53 -07002148 -x (--extra) <key=value>
2149 Add a key/value pair to the 'extras' dict, which device-specific extension
2150 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002151
Doug Zongkereef39442009-04-02 12:14:19 -07002152 -v (--verbose)
2153 Show command lines being executed.
2154
2155 -h (--help)
2156 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002157
2158 --logfile <file>
2159 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002160"""
2161
Kelvin Zhang0876c412020-06-23 15:06:58 -04002162
Doug Zongkereef39442009-04-02 12:14:19 -07002163def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002164 print(docstring.rstrip("\n"))
2165 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002166
2167
2168def ParseOptions(argv,
2169 docstring,
2170 extra_opts="", extra_long_opts=(),
2171 extra_option_handler=None):
2172 """Parse the options in argv and return any arguments that aren't
2173 flags. docstring is the calling module's docstring, to be displayed
2174 for errors and -h. extra_opts and extra_long_opts are for flags
2175 defined by the caller, which are processed by passing them to
2176 extra_option_handler."""
2177
2178 try:
2179 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002180 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002181 ["help", "verbose", "path=", "signapk_path=",
2182 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002183 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002184 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2185 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002186 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2187 "aftl_key_path=", "aftl_manufacturer_key_path=",
2188 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002189 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002190 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002191 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002192 sys.exit(2)
2193
Doug Zongkereef39442009-04-02 12:14:19 -07002194 for o, a in opts:
2195 if o in ("-h", "--help"):
2196 Usage(docstring)
2197 sys.exit()
2198 elif o in ("-v", "--verbose"):
2199 OPTIONS.verbose = True
2200 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002201 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002202 elif o in ("--signapk_path",):
2203 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002204 elif o in ("--signapk_shared_library_path",):
2205 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002206 elif o in ("--extra_signapk_args",):
2207 OPTIONS.extra_signapk_args = shlex.split(a)
2208 elif o in ("--java_path",):
2209 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002210 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002211 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002212 elif o in ("--android_jar_path",):
2213 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002214 elif o in ("--public_key_suffix",):
2215 OPTIONS.public_key_suffix = a
2216 elif o in ("--private_key_suffix",):
2217 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002218 elif o in ("--boot_signer_path",):
2219 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002220 elif o in ("--boot_signer_args",):
2221 OPTIONS.boot_signer_args = shlex.split(a)
2222 elif o in ("--verity_signer_path",):
2223 OPTIONS.verity_signer_path = a
2224 elif o in ("--verity_signer_args",):
2225 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002226 elif o in ("--aftl_tool_path",):
2227 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002228 elif o in ("--aftl_server",):
2229 OPTIONS.aftl_server = a
2230 elif o in ("--aftl_key_path",):
2231 OPTIONS.aftl_key_path = a
2232 elif o in ("--aftl_manufacturer_key_path",):
2233 OPTIONS.aftl_manufacturer_key_path = a
2234 elif o in ("--aftl_signer_helper",):
2235 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002236 elif o in ("-s", "--device_specific"):
2237 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002238 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002239 key, value = a.split("=", 1)
2240 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002241 elif o in ("--logfile",):
2242 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002243 else:
2244 if extra_option_handler is None or not extra_option_handler(o, a):
2245 assert False, "unknown option \"%s\"" % (o,)
2246
Doug Zongker85448772014-09-09 14:59:20 -07002247 if OPTIONS.search_path:
2248 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2249 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002250
2251 return args
2252
2253
Tao Bao4c851b12016-09-19 13:54:38 -07002254def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002255 """Make a temp file and add it to the list of things to be deleted
2256 when Cleanup() is called. Return the filename."""
2257 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2258 os.close(fd)
2259 OPTIONS.tempfiles.append(fn)
2260 return fn
2261
2262
Tao Bao1c830bf2017-12-25 10:43:47 -08002263def MakeTempDir(prefix='tmp', suffix=''):
2264 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2265
2266 Returns:
2267 The absolute pathname of the new directory.
2268 """
2269 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2270 OPTIONS.tempfiles.append(dir_name)
2271 return dir_name
2272
2273
Doug Zongkereef39442009-04-02 12:14:19 -07002274def Cleanup():
2275 for i in OPTIONS.tempfiles:
2276 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002277 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002278 else:
2279 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002280 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002281
2282
2283class PasswordManager(object):
2284 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002285 self.editor = os.getenv("EDITOR")
2286 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002287
2288 def GetPasswords(self, items):
2289 """Get passwords corresponding to each string in 'items',
2290 returning a dict. (The dict may have keys in addition to the
2291 values in 'items'.)
2292
2293 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2294 user edit that file to add more needed passwords. If no editor is
2295 available, or $ANDROID_PW_FILE isn't define, prompts the user
2296 interactively in the ordinary way.
2297 """
2298
2299 current = self.ReadFile()
2300
2301 first = True
2302 while True:
2303 missing = []
2304 for i in items:
2305 if i not in current or not current[i]:
2306 missing.append(i)
2307 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002308 if not missing:
2309 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002310
2311 for i in missing:
2312 current[i] = ""
2313
2314 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002315 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002316 if sys.version_info[0] >= 3:
2317 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002318 answer = raw_input("try to edit again? [y]> ").strip()
2319 if answer and answer[0] not in 'yY':
2320 raise RuntimeError("key passwords unavailable")
2321 first = False
2322
2323 current = self.UpdateAndReadFile(current)
2324
Kelvin Zhang0876c412020-06-23 15:06:58 -04002325 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002326 """Prompt the user to enter a value (password) for each key in
2327 'current' whose value is fales. Returns a new dict with all the
2328 values.
2329 """
2330 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002331 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002332 if v:
2333 result[k] = v
2334 else:
2335 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002336 result[k] = getpass.getpass(
2337 "Enter password for %s key> " % k).strip()
2338 if result[k]:
2339 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002340 return result
2341
2342 def UpdateAndReadFile(self, current):
2343 if not self.editor or not self.pwfile:
2344 return self.PromptResult(current)
2345
2346 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002347 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002348 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2349 f.write("# (Additional spaces are harmless.)\n\n")
2350
2351 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002352 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002353 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002354 f.write("[[[ %s ]]] %s\n" % (v, k))
2355 if not v and first_line is None:
2356 # position cursor on first line with no password.
2357 first_line = i + 4
2358 f.close()
2359
Tao Bao986ee862018-10-04 15:46:16 -07002360 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002361
2362 return self.ReadFile()
2363
2364 def ReadFile(self):
2365 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002366 if self.pwfile is None:
2367 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002368 try:
2369 f = open(self.pwfile, "r")
2370 for line in f:
2371 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002372 if not line or line[0] == '#':
2373 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002374 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2375 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002376 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002377 else:
2378 result[m.group(2)] = m.group(1)
2379 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002380 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002381 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002382 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002383 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002384
2385
Dan Albert8e0178d2015-01-27 15:53:15 -08002386def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2387 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002388
2389 # http://b/18015246
2390 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2391 # for files larger than 2GiB. We can work around this by adjusting their
2392 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2393 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2394 # it isn't clear to me exactly what circumstances cause this).
2395 # `zipfile.write()` must be used directly to work around this.
2396 #
2397 # This mess can be avoided if we port to python3.
2398 saved_zip64_limit = zipfile.ZIP64_LIMIT
2399 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2400
2401 if compress_type is None:
2402 compress_type = zip_file.compression
2403 if arcname is None:
2404 arcname = filename
2405
2406 saved_stat = os.stat(filename)
2407
2408 try:
2409 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2410 # file to be zipped and reset it when we're done.
2411 os.chmod(filename, perms)
2412
2413 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002414 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2415 # intentional. zip stores datetimes in local time without a time zone
2416 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2417 # in the zip archive.
2418 local_epoch = datetime.datetime.fromtimestamp(0)
2419 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002420 os.utime(filename, (timestamp, timestamp))
2421
2422 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2423 finally:
2424 os.chmod(filename, saved_stat.st_mode)
2425 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2426 zipfile.ZIP64_LIMIT = saved_zip64_limit
2427
2428
Tao Bao58c1b962015-05-20 09:32:18 -07002429def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002430 compress_type=None):
2431 """Wrap zipfile.writestr() function to work around the zip64 limit.
2432
2433 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2434 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2435 when calling crc32(bytes).
2436
2437 But it still works fine to write a shorter string into a large zip file.
2438 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2439 when we know the string won't be too long.
2440 """
2441
2442 saved_zip64_limit = zipfile.ZIP64_LIMIT
2443 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2444
2445 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2446 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002447 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002448 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002449 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002450 else:
Tao Baof3282b42015-04-01 11:21:55 -07002451 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002452 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2453 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2454 # such a case (since
2455 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2456 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2457 # permission bits. We follow the logic in Python 3 to get consistent
2458 # behavior between using the two versions.
2459 if not zinfo.external_attr:
2460 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002461
2462 # If compress_type is given, it overrides the value in zinfo.
2463 if compress_type is not None:
2464 zinfo.compress_type = compress_type
2465
Tao Bao58c1b962015-05-20 09:32:18 -07002466 # If perms is given, it has a priority.
2467 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002468 # If perms doesn't set the file type, mark it as a regular file.
2469 if perms & 0o770000 == 0:
2470 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002471 zinfo.external_attr = perms << 16
2472
Tao Baof3282b42015-04-01 11:21:55 -07002473 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002474 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2475
Dan Albert8b72aef2015-03-23 19:13:21 -07002476 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002477 zipfile.ZIP64_LIMIT = saved_zip64_limit
2478
2479
Tao Bao89d7ab22017-12-14 17:05:33 -08002480def ZipDelete(zip_filename, entries):
2481 """Deletes entries from a ZIP file.
2482
2483 Since deleting entries from a ZIP file is not supported, it shells out to
2484 'zip -d'.
2485
2486 Args:
2487 zip_filename: The name of the ZIP file.
2488 entries: The name of the entry, or the list of names to be deleted.
2489
2490 Raises:
2491 AssertionError: In case of non-zero return from 'zip'.
2492 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002493 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002494 entries = [entries]
2495 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002496 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002497
2498
Tao Baof3282b42015-04-01 11:21:55 -07002499def ZipClose(zip_file):
2500 # http://b/18015246
2501 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2502 # central directory.
2503 saved_zip64_limit = zipfile.ZIP64_LIMIT
2504 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2505
2506 zip_file.close()
2507
2508 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002509
2510
2511class DeviceSpecificParams(object):
2512 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002513
Doug Zongker05d3dea2009-06-22 11:32:31 -07002514 def __init__(self, **kwargs):
2515 """Keyword arguments to the constructor become attributes of this
2516 object, which is passed to all functions in the device-specific
2517 module."""
Tao Bao38884282019-07-10 22:20:56 -07002518 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002519 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002520 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002521
2522 if self.module is None:
2523 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002524 if not path:
2525 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002526 try:
2527 if os.path.isdir(path):
2528 info = imp.find_module("releasetools", [path])
2529 else:
2530 d, f = os.path.split(path)
2531 b, x = os.path.splitext(f)
2532 if x == ".py":
2533 f = b
2534 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002535 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002536 self.module = imp.load_module("device_specific", *info)
2537 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002538 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002539
2540 def _DoCall(self, function_name, *args, **kwargs):
2541 """Call the named function in the device-specific module, passing
2542 the given args and kwargs. The first argument to the call will be
2543 the DeviceSpecific object itself. If there is no module, or the
2544 module does not define the function, return the value of the
2545 'default' kwarg (which itself defaults to None)."""
2546 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002547 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002548 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2549
2550 def FullOTA_Assertions(self):
2551 """Called after emitting the block of assertions at the top of a
2552 full OTA package. Implementations can add whatever additional
2553 assertions they like."""
2554 return self._DoCall("FullOTA_Assertions")
2555
Doug Zongkere5ff5902012-01-17 10:55:37 -08002556 def FullOTA_InstallBegin(self):
2557 """Called at the start of full OTA installation."""
2558 return self._DoCall("FullOTA_InstallBegin")
2559
Yifan Hong10c530d2018-12-27 17:34:18 -08002560 def FullOTA_GetBlockDifferences(self):
2561 """Called during full OTA installation and verification.
2562 Implementation should return a list of BlockDifference objects describing
2563 the update on each additional partitions.
2564 """
2565 return self._DoCall("FullOTA_GetBlockDifferences")
2566
Doug Zongker05d3dea2009-06-22 11:32:31 -07002567 def FullOTA_InstallEnd(self):
2568 """Called at the end of full OTA installation; typically this is
2569 used to install the image for the device's baseband processor."""
2570 return self._DoCall("FullOTA_InstallEnd")
2571
2572 def IncrementalOTA_Assertions(self):
2573 """Called after emitting the block of assertions at the top of an
2574 incremental OTA package. Implementations can add whatever
2575 additional assertions they like."""
2576 return self._DoCall("IncrementalOTA_Assertions")
2577
Doug Zongkere5ff5902012-01-17 10:55:37 -08002578 def IncrementalOTA_VerifyBegin(self):
2579 """Called at the start of the verification phase of incremental
2580 OTA installation; additional checks can be placed here to abort
2581 the script before any changes are made."""
2582 return self._DoCall("IncrementalOTA_VerifyBegin")
2583
Doug Zongker05d3dea2009-06-22 11:32:31 -07002584 def IncrementalOTA_VerifyEnd(self):
2585 """Called at the end of the verification phase of incremental OTA
2586 installation; additional checks can be placed here to abort the
2587 script before any changes are made."""
2588 return self._DoCall("IncrementalOTA_VerifyEnd")
2589
Doug Zongkere5ff5902012-01-17 10:55:37 -08002590 def IncrementalOTA_InstallBegin(self):
2591 """Called at the start of incremental OTA installation (after
2592 verification is complete)."""
2593 return self._DoCall("IncrementalOTA_InstallBegin")
2594
Yifan Hong10c530d2018-12-27 17:34:18 -08002595 def IncrementalOTA_GetBlockDifferences(self):
2596 """Called during incremental OTA installation and verification.
2597 Implementation should return a list of BlockDifference objects describing
2598 the update on each additional partitions.
2599 """
2600 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2601
Doug Zongker05d3dea2009-06-22 11:32:31 -07002602 def IncrementalOTA_InstallEnd(self):
2603 """Called at the end of incremental OTA installation; typically
2604 this is used to install the image for the device's baseband
2605 processor."""
2606 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002607
Tao Bao9bc6bb22015-11-09 16:58:28 -08002608 def VerifyOTA_Assertions(self):
2609 return self._DoCall("VerifyOTA_Assertions")
2610
Tao Bao76def242017-11-21 09:25:31 -08002611
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002612class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002613 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002614 self.name = name
2615 self.data = data
2616 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002617 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002618 self.sha1 = sha1(data).hexdigest()
2619
2620 @classmethod
2621 def FromLocalFile(cls, name, diskname):
2622 f = open(diskname, "rb")
2623 data = f.read()
2624 f.close()
2625 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002626
2627 def WriteToTemp(self):
2628 t = tempfile.NamedTemporaryFile()
2629 t.write(self.data)
2630 t.flush()
2631 return t
2632
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002633 def WriteToDir(self, d):
2634 with open(os.path.join(d, self.name), "wb") as fp:
2635 fp.write(self.data)
2636
Geremy Condra36bd3652014-02-06 19:45:10 -08002637 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002638 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002639
Tao Bao76def242017-11-21 09:25:31 -08002640
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002641DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002642 ".gz": "imgdiff",
2643 ".zip": ["imgdiff", "-z"],
2644 ".jar": ["imgdiff", "-z"],
2645 ".apk": ["imgdiff", "-z"],
2646 ".img": "imgdiff",
2647}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002648
Tao Bao76def242017-11-21 09:25:31 -08002649
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002650class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002651 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002652 self.tf = tf
2653 self.sf = sf
2654 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002655 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002656
2657 def ComputePatch(self):
2658 """Compute the patch (as a string of data) needed to turn sf into
2659 tf. Returns the same tuple as GetPatch()."""
2660
2661 tf = self.tf
2662 sf = self.sf
2663
Doug Zongker24cd2802012-08-14 16:36:15 -07002664 if self.diff_program:
2665 diff_program = self.diff_program
2666 else:
2667 ext = os.path.splitext(tf.name)[1]
2668 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002669
2670 ttemp = tf.WriteToTemp()
2671 stemp = sf.WriteToTemp()
2672
2673 ext = os.path.splitext(tf.name)[1]
2674
2675 try:
2676 ptemp = tempfile.NamedTemporaryFile()
2677 if isinstance(diff_program, list):
2678 cmd = copy.copy(diff_program)
2679 else:
2680 cmd = [diff_program]
2681 cmd.append(stemp.name)
2682 cmd.append(ttemp.name)
2683 cmd.append(ptemp.name)
2684 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002685 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002686
Doug Zongkerf8340082014-08-05 10:39:37 -07002687 def run():
2688 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002689 if e:
2690 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002691 th = threading.Thread(target=run)
2692 th.start()
2693 th.join(timeout=300) # 5 mins
2694 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002695 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002696 p.terminate()
2697 th.join(5)
2698 if th.is_alive():
2699 p.kill()
2700 th.join()
2701
Tianjie Xua2a9f992018-01-05 15:15:54 -08002702 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002703 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002704 self.patch = None
2705 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002706 diff = ptemp.read()
2707 finally:
2708 ptemp.close()
2709 stemp.close()
2710 ttemp.close()
2711
2712 self.patch = diff
2713 return self.tf, self.sf, self.patch
2714
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002715 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002716 """Returns a tuple of (target_file, source_file, patch_data).
2717
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002718 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002719 computing the patch failed.
2720 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002721 return self.tf, self.sf, self.patch
2722
2723
2724def ComputeDifferences(diffs):
2725 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002726 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002727
2728 # Do the largest files first, to try and reduce the long-pole effect.
2729 by_size = [(i.tf.size, i) for i in diffs]
2730 by_size.sort(reverse=True)
2731 by_size = [i[1] for i in by_size]
2732
2733 lock = threading.Lock()
2734 diff_iter = iter(by_size) # accessed under lock
2735
2736 def worker():
2737 try:
2738 lock.acquire()
2739 for d in diff_iter:
2740 lock.release()
2741 start = time.time()
2742 d.ComputePatch()
2743 dur = time.time() - start
2744 lock.acquire()
2745
2746 tf, sf, patch = d.GetPatch()
2747 if sf.name == tf.name:
2748 name = tf.name
2749 else:
2750 name = "%s (%s)" % (tf.name, sf.name)
2751 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002752 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002753 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002754 logger.info(
2755 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2756 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002757 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002758 except Exception:
2759 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002760 raise
2761
2762 # start worker threads; wait for them all to finish.
2763 threads = [threading.Thread(target=worker)
2764 for i in range(OPTIONS.worker_threads)]
2765 for th in threads:
2766 th.start()
2767 while threads:
2768 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002769
2770
Dan Albert8b72aef2015-03-23 19:13:21 -07002771class BlockDifference(object):
2772 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002773 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002774 self.tgt = tgt
2775 self.src = src
2776 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002777 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002778 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002779
Tao Baodd2a5892015-03-12 12:32:37 -07002780 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002781 version = max(
2782 int(i) for i in
2783 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002784 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002785 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002786
Tianjie Xu41976c72019-07-03 13:57:01 -07002787 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2788 version=self.version,
2789 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002790 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002791 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002792 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002793 self.touched_src_ranges = b.touched_src_ranges
2794 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002795
Yifan Hong10c530d2018-12-27 17:34:18 -08002796 # On devices with dynamic partitions, for new partitions,
2797 # src is None but OPTIONS.source_info_dict is not.
2798 if OPTIONS.source_info_dict is None:
2799 is_dynamic_build = OPTIONS.info_dict.get(
2800 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002801 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002802 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002803 is_dynamic_build = OPTIONS.source_info_dict.get(
2804 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002805 is_dynamic_source = partition in shlex.split(
2806 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002807
Yifan Hongbb2658d2019-01-25 12:30:58 -08002808 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002809 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2810
Yifan Hongbb2658d2019-01-25 12:30:58 -08002811 # For dynamic partitions builds, check partition list in both source
2812 # and target build because new partitions may be added, and existing
2813 # partitions may be removed.
2814 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2815
Yifan Hong10c530d2018-12-27 17:34:18 -08002816 if is_dynamic:
2817 self.device = 'map_partition("%s")' % partition
2818 else:
2819 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002820 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2821 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002822 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002823 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2824 OPTIONS.source_info_dict)
2825 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002826
Tao Baod8d14be2016-02-04 14:26:02 -08002827 @property
2828 def required_cache(self):
2829 return self._required_cache
2830
Tao Bao76def242017-11-21 09:25:31 -08002831 def WriteScript(self, script, output_zip, progress=None,
2832 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002833 if not self.src:
2834 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002835 script.Print("Patching %s image unconditionally..." % (self.partition,))
2836 else:
2837 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002838
Dan Albert8b72aef2015-03-23 19:13:21 -07002839 if progress:
2840 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002841 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002842
2843 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002844 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002845
Tao Bao9bc6bb22015-11-09 16:58:28 -08002846 def WriteStrictVerifyScript(self, script):
2847 """Verify all the blocks in the care_map, including clobbered blocks.
2848
2849 This differs from the WriteVerifyScript() function: a) it prints different
2850 error messages; b) it doesn't allow half-way updated images to pass the
2851 verification."""
2852
2853 partition = self.partition
2854 script.Print("Verifying %s..." % (partition,))
2855 ranges = self.tgt.care_map
2856 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002857 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002858 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2859 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002860 self.device, ranges_str,
2861 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002862 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002863 script.AppendExtra("")
2864
Tao Baod522bdc2016-04-12 15:53:16 -07002865 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002866 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002867
2868 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002869 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002870 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002871
2872 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002873 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002874 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002875 ranges = self.touched_src_ranges
2876 expected_sha1 = self.touched_src_sha1
2877 else:
2878 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2879 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002880
2881 # No blocks to be checked, skipping.
2882 if not ranges:
2883 return
2884
Tao Bao5ece99d2015-05-12 11:42:31 -07002885 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002886 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002887 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002888 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2889 '"%s.patch.dat")) then' % (
2890 self.device, ranges_str, expected_sha1,
2891 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002892 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002893 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002894
Tianjie Xufc3422a2015-12-15 11:53:59 -08002895 if self.version >= 4:
2896
2897 # Bug: 21124327
2898 # When generating incrementals for the system and vendor partitions in
2899 # version 4 or newer, explicitly check the first block (which contains
2900 # the superblock) of the partition to see if it's what we expect. If
2901 # this check fails, give an explicit log message about the partition
2902 # having been remounted R/W (the most likely explanation).
2903 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002904 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002905
2906 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002907 if partition == "system":
2908 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2909 else:
2910 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002911 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002912 'ifelse (block_image_recover({device}, "{ranges}") && '
2913 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002914 'package_extract_file("{partition}.transfer.list"), '
2915 '"{partition}.new.dat", "{partition}.patch.dat"), '
2916 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002917 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002918 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002919 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002920
Tao Baodd2a5892015-03-12 12:32:37 -07002921 # Abort the OTA update. Note that the incremental OTA cannot be applied
2922 # even if it may match the checksum of the target partition.
2923 # a) If version < 3, operations like move and erase will make changes
2924 # unconditionally and damage the partition.
2925 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002926 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002927 if partition == "system":
2928 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2929 else:
2930 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2931 script.AppendExtra((
2932 'abort("E%d: %s partition has unexpected contents");\n'
2933 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002934
Yifan Hong10c530d2018-12-27 17:34:18 -08002935 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002936 partition = self.partition
2937 script.Print('Verifying the updated %s image...' % (partition,))
2938 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2939 ranges = self.tgt.care_map
2940 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002941 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002942 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002943 self.device, ranges_str,
2944 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002945
2946 # Bug: 20881595
2947 # Verify that extended blocks are really zeroed out.
2948 if self.tgt.extended:
2949 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002950 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002951 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002952 self.device, ranges_str,
2953 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002954 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002955 if partition == "system":
2956 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2957 else:
2958 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002959 script.AppendExtra(
2960 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002961 ' abort("E%d: %s partition has unexpected non-zero contents after '
2962 'OTA update");\n'
2963 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002964 else:
2965 script.Print('Verified the updated %s image.' % (partition,))
2966
Tianjie Xu209db462016-05-24 17:34:52 -07002967 if partition == "system":
2968 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2969 else:
2970 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2971
Tao Bao5fcaaef2015-06-01 13:40:49 -07002972 script.AppendExtra(
2973 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002974 ' abort("E%d: %s partition has unexpected contents after OTA '
2975 'update");\n'
2976 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002977
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002978 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002979 ZipWrite(output_zip,
2980 '{}.transfer.list'.format(self.path),
2981 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002982
Tao Bao76def242017-11-21 09:25:31 -08002983 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2984 # its size. Quailty 9 almost triples the compression time but doesn't
2985 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002986 # zip | brotli(quality 6) | brotli(quality 9)
2987 # compressed_size: 942M | 869M (~8% reduced) | 854M
2988 # compression_time: 75s | 265s | 719s
2989 # decompression_time: 15s | 25s | 25s
2990
2991 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002992 brotli_cmd = ['brotli', '--quality=6',
2993 '--output={}.new.dat.br'.format(self.path),
2994 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002995 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002996 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002997
2998 new_data_name = '{}.new.dat.br'.format(self.partition)
2999 ZipWrite(output_zip,
3000 '{}.new.dat.br'.format(self.path),
3001 new_data_name,
3002 compress_type=zipfile.ZIP_STORED)
3003 else:
3004 new_data_name = '{}.new.dat'.format(self.partition)
3005 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3006
Dan Albert8e0178d2015-01-27 15:53:15 -08003007 ZipWrite(output_zip,
3008 '{}.patch.dat'.format(self.path),
3009 '{}.patch.dat'.format(self.partition),
3010 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003011
Tianjie Xu209db462016-05-24 17:34:52 -07003012 if self.partition == "system":
3013 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3014 else:
3015 code = ErrorCode.VENDOR_UPDATE_FAILURE
3016
Yifan Hong10c530d2018-12-27 17:34:18 -08003017 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003018 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003019 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003020 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003021 device=self.device, partition=self.partition,
3022 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003023 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003024
Kelvin Zhang0876c412020-06-23 15:06:58 -04003025 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003026 data = source.ReadRangeSet(ranges)
3027 ctx = sha1()
3028
3029 for p in data:
3030 ctx.update(p)
3031
3032 return ctx.hexdigest()
3033
Kelvin Zhang0876c412020-06-23 15:06:58 -04003034 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003035 """Return the hash value for all zero blocks."""
3036 zero_block = '\x00' * 4096
3037 ctx = sha1()
3038 for _ in range(num_blocks):
3039 ctx.update(zero_block)
3040
3041 return ctx.hexdigest()
3042
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003043
Tianjie Xu41976c72019-07-03 13:57:01 -07003044# Expose these two classes to support vendor-specific scripts
3045DataImage = images.DataImage
3046EmptyImage = images.EmptyImage
3047
Tao Bao76def242017-11-21 09:25:31 -08003048
Doug Zongker96a57e72010-09-26 14:57:41 -07003049# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003050PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003051 "ext4": "EMMC",
3052 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003053 "f2fs": "EMMC",
3054 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003055}
Doug Zongker96a57e72010-09-26 14:57:41 -07003056
Kelvin Zhang0876c412020-06-23 15:06:58 -04003057
Yifan Hongbdb32012020-05-07 12:38:53 -07003058def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3059 """
3060 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3061 backwards compatibility. It aborts if the fstab entry has slotselect option
3062 (unless check_no_slot is explicitly set to False).
3063 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003064 fstab = info["fstab"]
3065 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003066 if check_no_slot:
3067 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003068 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003069 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3070 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003071 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003072
3073
Yifan Hongbdb32012020-05-07 12:38:53 -07003074def GetTypeAndDeviceExpr(mount_point, info):
3075 """
3076 Return the filesystem of the partition, and an edify expression that evaluates
3077 to the device at runtime.
3078 """
3079 fstab = info["fstab"]
3080 if fstab:
3081 p = fstab[mount_point]
3082 device_expr = '"%s"' % fstab[mount_point].device
3083 if p.slotselect:
3084 device_expr = 'add_slot_suffix(%s)' % device_expr
3085 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003086 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003087
3088
3089def GetEntryForDevice(fstab, device):
3090 """
3091 Returns:
3092 The first entry in fstab whose device is the given value.
3093 """
3094 if not fstab:
3095 return None
3096 for mount_point in fstab:
3097 if fstab[mount_point].device == device:
3098 return fstab[mount_point]
3099 return None
3100
Kelvin Zhang0876c412020-06-23 15:06:58 -04003101
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003102def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003103 """Parses and converts a PEM-encoded certificate into DER-encoded.
3104
3105 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3106
3107 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003108 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003109 """
3110 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003111 save = False
3112 for line in data.split("\n"):
3113 if "--END CERTIFICATE--" in line:
3114 break
3115 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003116 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003117 if "--BEGIN CERTIFICATE--" in line:
3118 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003119 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003120 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003121
Tao Bao04e1f012018-02-04 12:13:35 -08003122
3123def ExtractPublicKey(cert):
3124 """Extracts the public key (PEM-encoded) from the given certificate file.
3125
3126 Args:
3127 cert: The certificate filename.
3128
3129 Returns:
3130 The public key string.
3131
3132 Raises:
3133 AssertionError: On non-zero return from 'openssl'.
3134 """
3135 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3136 # While openssl 1.1 writes the key into the given filename followed by '-out',
3137 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3138 # stdout instead.
3139 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3140 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3141 pubkey, stderrdata = proc.communicate()
3142 assert proc.returncode == 0, \
3143 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3144 return pubkey
3145
3146
Tao Bao1ac886e2019-06-26 11:58:22 -07003147def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003148 """Extracts the AVB public key from the given public or private key.
3149
3150 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003151 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003152 key: The input key file, which should be PEM-encoded public or private key.
3153
3154 Returns:
3155 The path to the extracted AVB public key file.
3156 """
3157 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3158 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003159 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003160 return output
3161
3162
Doug Zongker412c02f2014-02-13 10:58:24 -08003163def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3164 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003165 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003166
Tao Bao6d5d6232018-03-09 17:04:42 -08003167 Most of the space in the boot and recovery images is just the kernel, which is
3168 identical for the two, so the resulting patch should be efficient. Add it to
3169 the output zip, along with a shell script that is run from init.rc on first
3170 boot to actually do the patching and install the new recovery image.
3171
3172 Args:
3173 input_dir: The top-level input directory of the target-files.zip.
3174 output_sink: The callback function that writes the result.
3175 recovery_img: File object for the recovery image.
3176 boot_img: File objects for the boot image.
3177 info_dict: A dict returned by common.LoadInfoDict() on the input
3178 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003179 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003180 if info_dict is None:
3181 info_dict = OPTIONS.info_dict
3182
Tao Bao6d5d6232018-03-09 17:04:42 -08003183 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003184 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3185
3186 if board_uses_vendorimage:
3187 # In this case, the output sink is rooted at VENDOR
3188 recovery_img_path = "etc/recovery.img"
3189 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3190 sh_dir = "bin"
3191 else:
3192 # In this case the output sink is rooted at SYSTEM
3193 recovery_img_path = "vendor/etc/recovery.img"
3194 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3195 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003196
Tao Baof2cffbd2015-07-22 12:33:18 -07003197 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003198 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003199
3200 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003201 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003202 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003203 # With system-root-image, boot and recovery images will have mismatching
3204 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3205 # to handle such a case.
3206 if system_root_image:
3207 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003208 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003209 assert not os.path.exists(path)
3210 else:
3211 diff_program = ["imgdiff"]
3212 if os.path.exists(path):
3213 diff_program.append("-b")
3214 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003215 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003216 else:
3217 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003218
3219 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3220 _, _, patch = d.ComputePatch()
3221 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003222
Dan Albertebb19aa2015-03-27 19:11:53 -07003223 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003224 # The following GetTypeAndDevice()s need to use the path in the target
3225 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003226 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3227 check_no_slot=False)
3228 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3229 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003230 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003231 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003232
Tao Baof2cffbd2015-07-22 12:33:18 -07003233 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003234
3235 # Note that we use /vendor to refer to the recovery resources. This will
3236 # work for a separate vendor partition mounted at /vendor or a
3237 # /system/vendor subdirectory on the system partition, for which init will
3238 # create a symlink from /vendor to /system/vendor.
3239
3240 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003241if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3242 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003243 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003244 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3245 log -t recovery "Installing new recovery image: succeeded" || \\
3246 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003247else
3248 log -t recovery "Recovery image already installed"
3249fi
3250""" % {'type': recovery_type,
3251 'device': recovery_device,
3252 'sha1': recovery_img.sha1,
3253 'size': recovery_img.size}
3254 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003255 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003256if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3257 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003258 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003259 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3260 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3261 log -t recovery "Installing new recovery image: succeeded" || \\
3262 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003263else
3264 log -t recovery "Recovery image already installed"
3265fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003266""" % {'boot_size': boot_img.size,
3267 'boot_sha1': boot_img.sha1,
3268 'recovery_size': recovery_img.size,
3269 'recovery_sha1': recovery_img.sha1,
3270 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003271 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003272 'recovery_type': recovery_type,
3273 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003274 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003275
Bill Peckhame868aec2019-09-17 17:06:47 -07003276 # The install script location moved from /system/etc to /system/bin in the L
3277 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3278 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003279
Tao Bao32fcdab2018-10-12 10:30:39 -07003280 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003281
Tao Baoda30cfa2017-12-01 16:19:46 -08003282 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003283
3284
3285class DynamicPartitionUpdate(object):
3286 def __init__(self, src_group=None, tgt_group=None, progress=None,
3287 block_difference=None):
3288 self.src_group = src_group
3289 self.tgt_group = tgt_group
3290 self.progress = progress
3291 self.block_difference = block_difference
3292
3293 @property
3294 def src_size(self):
3295 if not self.block_difference:
3296 return 0
3297 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3298
3299 @property
3300 def tgt_size(self):
3301 if not self.block_difference:
3302 return 0
3303 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3304
3305 @staticmethod
3306 def _GetSparseImageSize(img):
3307 if not img:
3308 return 0
3309 return img.blocksize * img.total_blocks
3310
3311
3312class DynamicGroupUpdate(object):
3313 def __init__(self, src_size=None, tgt_size=None):
3314 # None: group does not exist. 0: no size limits.
3315 self.src_size = src_size
3316 self.tgt_size = tgt_size
3317
3318
3319class DynamicPartitionsDifference(object):
3320 def __init__(self, info_dict, block_diffs, progress_dict=None,
3321 source_info_dict=None):
3322 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003323 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003324
3325 self._remove_all_before_apply = False
3326 if source_info_dict is None:
3327 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003328 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003329
Tao Baof1113e92019-06-18 12:10:14 -07003330 block_diff_dict = collections.OrderedDict(
3331 [(e.partition, e) for e in block_diffs])
3332
Yifan Hong10c530d2018-12-27 17:34:18 -08003333 assert len(block_diff_dict) == len(block_diffs), \
3334 "Duplicated BlockDifference object for {}".format(
3335 [partition for partition, count in
3336 collections.Counter(e.partition for e in block_diffs).items()
3337 if count > 1])
3338
Yifan Hong79997e52019-01-23 16:56:19 -08003339 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003340
3341 for p, block_diff in block_diff_dict.items():
3342 self._partition_updates[p] = DynamicPartitionUpdate()
3343 self._partition_updates[p].block_difference = block_diff
3344
3345 for p, progress in progress_dict.items():
3346 if p in self._partition_updates:
3347 self._partition_updates[p].progress = progress
3348
3349 tgt_groups = shlex.split(info_dict.get(
3350 "super_partition_groups", "").strip())
3351 src_groups = shlex.split(source_info_dict.get(
3352 "super_partition_groups", "").strip())
3353
3354 for g in tgt_groups:
3355 for p in shlex.split(info_dict.get(
3356 "super_%s_partition_list" % g, "").strip()):
3357 assert p in self._partition_updates, \
3358 "{} is in target super_{}_partition_list but no BlockDifference " \
3359 "object is provided.".format(p, g)
3360 self._partition_updates[p].tgt_group = g
3361
3362 for g in src_groups:
3363 for p in shlex.split(source_info_dict.get(
3364 "super_%s_partition_list" % g, "").strip()):
3365 assert p in self._partition_updates, \
3366 "{} is in source super_{}_partition_list but no BlockDifference " \
3367 "object is provided.".format(p, g)
3368 self._partition_updates[p].src_group = g
3369
Yifan Hong45433e42019-01-18 13:55:25 -08003370 target_dynamic_partitions = set(shlex.split(info_dict.get(
3371 "dynamic_partition_list", "").strip()))
3372 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3373 if u.tgt_size)
3374 assert block_diffs_with_target == target_dynamic_partitions, \
3375 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3376 list(target_dynamic_partitions), list(block_diffs_with_target))
3377
3378 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3379 "dynamic_partition_list", "").strip()))
3380 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3381 if u.src_size)
3382 assert block_diffs_with_source == source_dynamic_partitions, \
3383 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3384 list(source_dynamic_partitions), list(block_diffs_with_source))
3385
Yifan Hong10c530d2018-12-27 17:34:18 -08003386 if self._partition_updates:
3387 logger.info("Updating dynamic partitions %s",
3388 self._partition_updates.keys())
3389
Yifan Hong79997e52019-01-23 16:56:19 -08003390 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003391
3392 for g in tgt_groups:
3393 self._group_updates[g] = DynamicGroupUpdate()
3394 self._group_updates[g].tgt_size = int(info_dict.get(
3395 "super_%s_group_size" % g, "0").strip())
3396
3397 for g in src_groups:
3398 if g not in self._group_updates:
3399 self._group_updates[g] = DynamicGroupUpdate()
3400 self._group_updates[g].src_size = int(source_info_dict.get(
3401 "super_%s_group_size" % g, "0").strip())
3402
3403 self._Compute()
3404
3405 def WriteScript(self, script, output_zip, write_verify_script=False):
3406 script.Comment('--- Start patching dynamic partitions ---')
3407 for p, u in self._partition_updates.items():
3408 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3409 script.Comment('Patch partition %s' % p)
3410 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3411 write_verify_script=False)
3412
3413 op_list_path = MakeTempFile()
3414 with open(op_list_path, 'w') as f:
3415 for line in self._op_list:
3416 f.write('{}\n'.format(line))
3417
3418 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3419
3420 script.Comment('Update dynamic partition metadata')
3421 script.AppendExtra('assert(update_dynamic_partitions('
3422 'package_extract_file("dynamic_partitions_op_list")));')
3423
3424 if write_verify_script:
3425 for p, u in self._partition_updates.items():
3426 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3427 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003428 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003429
3430 for p, u in self._partition_updates.items():
3431 if u.tgt_size and u.src_size <= u.tgt_size:
3432 script.Comment('Patch partition %s' % p)
3433 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3434 write_verify_script=write_verify_script)
3435 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003436 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003437
3438 script.Comment('--- End patching dynamic partitions ---')
3439
3440 def _Compute(self):
3441 self._op_list = list()
3442
3443 def append(line):
3444 self._op_list.append(line)
3445
3446 def comment(line):
3447 self._op_list.append("# %s" % line)
3448
3449 if self._remove_all_before_apply:
3450 comment('Remove all existing dynamic partitions and groups before '
3451 'applying full OTA')
3452 append('remove_all_groups')
3453
3454 for p, u in self._partition_updates.items():
3455 if u.src_group and not u.tgt_group:
3456 append('remove %s' % p)
3457
3458 for p, u in self._partition_updates.items():
3459 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3460 comment('Move partition %s from %s to default' % (p, u.src_group))
3461 append('move %s default' % p)
3462
3463 for p, u in self._partition_updates.items():
3464 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3465 comment('Shrink partition %s from %d to %d' %
3466 (p, u.src_size, u.tgt_size))
3467 append('resize %s %s' % (p, u.tgt_size))
3468
3469 for g, u in self._group_updates.items():
3470 if u.src_size is not None and u.tgt_size is None:
3471 append('remove_group %s' % g)
3472 if (u.src_size is not None and u.tgt_size is not None and
3473 u.src_size > u.tgt_size):
3474 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3475 append('resize_group %s %d' % (g, u.tgt_size))
3476
3477 for g, u in self._group_updates.items():
3478 if u.src_size is None and u.tgt_size is not None:
3479 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3480 append('add_group %s %d' % (g, u.tgt_size))
3481 if (u.src_size is not None and u.tgt_size is not None and
3482 u.src_size < u.tgt_size):
3483 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3484 append('resize_group %s %d' % (g, u.tgt_size))
3485
3486 for p, u in self._partition_updates.items():
3487 if u.tgt_group and not u.src_group:
3488 comment('Add partition %s to group %s' % (p, u.tgt_group))
3489 append('add %s %s' % (p, u.tgt_group))
3490
3491 for p, u in self._partition_updates.items():
3492 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003493 comment('Grow partition %s from %d to %d' %
3494 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003495 append('resize %s %d' % (p, u.tgt_size))
3496
3497 for p, u in self._partition_updates.items():
3498 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3499 comment('Move partition %s from default to %s' %
3500 (p, u.tgt_group))
3501 append('move %s %s' % (p, u.tgt_group))