blob: 9ac2cc66aa3ae346d03310988c78691a5b4786d2 [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
Dan Albert8b72aef2015-03-23 19:13:21 -070099
100
101OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700102
Tao Bao71197512018-10-11 14:08:45 -0700103# The block size that's used across the releasetools scripts.
104BLOCK_SIZE = 4096
105
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800106# Values for "certificate" in apkcerts that mean special things.
107SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
108
Tao Bao5cc0abb2019-03-21 10:18:05 -0700109# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
110# that system_other is not in the list because we don't want to include its
111# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900112AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700113 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
114 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800115
Tao Bao08c190f2019-06-03 23:07:58 -0700116# Chained VBMeta partitions.
117AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
118
Tianjie Xu861f4132018-09-12 11:49:33 -0700119# Partitions that should have their care_map added to META/care_map.pb
Yifan Hongcfb917a2020-05-07 14:58:20 -0700120PARTITIONS_WITH_CARE_MAP = (
121 'system',
122 'vendor',
123 'product',
124 'system_ext',
125 'odm',
126 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700127 'odm_dlkm',
Yifan Hongcfb917a2020-05-07 14:58:20 -0700128)
Tianjie Xu861f4132018-09-12 11:49:33 -0700129
130
Tianjie Xu209db462016-05-24 17:34:52 -0700131class ErrorCode(object):
132 """Define error_codes for failures that happen during the actual
133 update package installation.
134
135 Error codes 0-999 are reserved for failures before the package
136 installation (i.e. low battery, package verification failure).
137 Detailed code in 'bootable/recovery/error_code.h' """
138
139 SYSTEM_VERIFICATION_FAILURE = 1000
140 SYSTEM_UPDATE_FAILURE = 1001
141 SYSTEM_UNEXPECTED_CONTENTS = 1002
142 SYSTEM_NONZERO_CONTENTS = 1003
143 SYSTEM_RECOVER_FAILURE = 1004
144 VENDOR_VERIFICATION_FAILURE = 2000
145 VENDOR_UPDATE_FAILURE = 2001
146 VENDOR_UNEXPECTED_CONTENTS = 2002
147 VENDOR_NONZERO_CONTENTS = 2003
148 VENDOR_RECOVER_FAILURE = 2004
149 OEM_PROP_MISMATCH = 3000
150 FINGERPRINT_MISMATCH = 3001
151 THUMBPRINT_MISMATCH = 3002
152 OLDER_BUILD = 3003
153 DEVICE_MISMATCH = 3004
154 BAD_PATCH_FILE = 3005
155 INSUFFICIENT_CACHE_SPACE = 3006
156 TUNE_PARTITION_FAILURE = 3007
157 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800158
Tao Bao80921982018-03-21 21:02:19 -0700159
Dan Albert8b72aef2015-03-23 19:13:21 -0700160class ExternalError(RuntimeError):
161 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700162
163
Tao Bao32fcdab2018-10-12 10:30:39 -0700164def InitLogging():
165 DEFAULT_LOGGING_CONFIG = {
166 'version': 1,
167 'disable_existing_loggers': False,
168 'formatters': {
169 'standard': {
170 'format':
171 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
172 'datefmt': '%Y-%m-%d %H:%M:%S',
173 },
174 },
175 'handlers': {
176 'default': {
177 'class': 'logging.StreamHandler',
178 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700179 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700180 },
181 },
182 'loggers': {
183 '': {
184 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700185 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700186 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 }
188 }
189 }
190 env_config = os.getenv('LOGGING_CONFIG')
191 if env_config:
192 with open(env_config) as f:
193 config = json.load(f)
194 else:
195 config = DEFAULT_LOGGING_CONFIG
196
197 # Increase the logging level for verbose mode.
198 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700199 config = copy.deepcopy(config)
200 config['handlers']['default']['level'] = 'INFO'
201
202 if OPTIONS.logfile:
203 config = copy.deepcopy(config)
204 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400205 'class': 'logging.FileHandler',
206 'formatter': 'standard',
207 'level': 'INFO',
208 'mode': 'w',
209 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700210 }
211 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700212
213 logging.config.dictConfig(config)
214
215
Tao Bao39451582017-05-04 11:10:47 -0700216def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700217 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700218
Tao Bao73dd4f42018-10-04 16:25:33 -0700219 Args:
220 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700221 verbose: Whether the commands should be shown. Default to the global
222 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700223 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
224 stdin, etc. stdout and stderr will default to subprocess.PIPE and
225 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800226 universal_newlines will default to True, as most of the users in
227 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700228
229 Returns:
230 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700231 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700232 if 'stdout' not in kwargs and 'stderr' not in kwargs:
233 kwargs['stdout'] = subprocess.PIPE
234 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800235 if 'universal_newlines' not in kwargs:
236 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700237 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400238 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700239 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700240 return subprocess.Popen(args, **kwargs)
241
242
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800243def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800244 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800245
246 Args:
247 args: The command represented as a list of strings.
248 verbose: Whether the commands should be shown. Default to the global
249 verbosity if unspecified.
250 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
251 stdin, etc. stdout and stderr will default to subprocess.PIPE and
252 subprocess.STDOUT respectively unless caller specifies any of them.
253
Bill Peckham889b0c62019-02-21 18:53:37 -0800254 Raises:
255 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800256 """
257 proc = Run(args, verbose=verbose, **kwargs)
258 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800259
260 if proc.returncode != 0:
261 raise ExternalError(
262 "Failed to run command '{}' (exit code {})".format(
263 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800264
265
Tao Bao986ee862018-10-04 15:46:16 -0700266def RunAndCheckOutput(args, verbose=None, **kwargs):
267 """Runs the given command and returns the output.
268
269 Args:
270 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700271 verbose: Whether the commands should be shown. Default to the global
272 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700273 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
274 stdin, etc. stdout and stderr will default to subprocess.PIPE and
275 subprocess.STDOUT respectively unless caller specifies any of them.
276
277 Returns:
278 The output string.
279
280 Raises:
281 ExternalError: On non-zero exit from the command.
282 """
Tao Bao986ee862018-10-04 15:46:16 -0700283 proc = Run(args, verbose=verbose, **kwargs)
284 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800285 if output is None:
286 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700287 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400288 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700289 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700290 if proc.returncode != 0:
291 raise ExternalError(
292 "Failed to run command '{}' (exit code {}):\n{}".format(
293 args, proc.returncode, output))
294 return output
295
296
Tao Baoc765cca2018-01-31 17:32:40 -0800297def RoundUpTo4K(value):
298 rounded_up = value + 4095
299 return rounded_up - (rounded_up % 4096)
300
301
Ying Wang7e6d4e42010-12-13 16:25:36 -0800302def CloseInheritedPipes():
303 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
304 before doing other work."""
305 if platform.system() != "Darwin":
306 return
307 for d in range(3, 1025):
308 try:
309 stat = os.fstat(d)
310 if stat is not None:
311 pipebit = stat[0] & 0x1000
312 if pipebit != 0:
313 os.close(d)
314 except OSError:
315 pass
316
317
Tao Bao1c320f82019-10-04 23:25:12 -0700318class BuildInfo(object):
319 """A class that holds the information for a given build.
320
321 This class wraps up the property querying for a given source or target build.
322 It abstracts away the logic of handling OEM-specific properties, and caches
323 the commonly used properties such as fingerprint.
324
325 There are two types of info dicts: a) build-time info dict, which is generated
326 at build time (i.e. included in a target_files zip); b) OEM info dict that is
327 specified at package generation time (via command line argument
328 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
329 having "oem_fingerprint_properties" in build-time info dict), all the queries
330 would be answered based on build-time info dict only. Otherwise if using
331 OEM-specific properties, some of them will be calculated from two info dicts.
332
333 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800334 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700335
336 Attributes:
337 info_dict: The build-time info dict.
338 is_ab: Whether it's a build that uses A/B OTA.
339 oem_dicts: A list of OEM dicts.
340 oem_props: A list of OEM properties that should be read from OEM dicts; None
341 if the build doesn't use any OEM-specific property.
342 fingerprint: The fingerprint of the build, which would be calculated based
343 on OEM properties if applicable.
344 device: The device name, which could come from OEM dicts if applicable.
345 """
346
347 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
348 "ro.product.manufacturer", "ro.product.model",
349 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700350 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
351 "product", "odm", "vendor", "system_ext", "system"]
352 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
353 "product", "product_services", "odm", "vendor", "system"]
354 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700355
Tao Bao3ed35d32019-10-07 20:48:48 -0700356 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700357 """Initializes a BuildInfo instance with the given dicts.
358
359 Note that it only wraps up the given dicts, without making copies.
360
361 Arguments:
362 info_dict: The build-time info dict.
363 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
364 that it always uses the first dict to calculate the fingerprint or the
365 device name. The rest would be used for asserting OEM properties only
366 (e.g. one package can be installed on one of these devices).
367
368 Raises:
369 ValueError: On invalid inputs.
370 """
371 self.info_dict = info_dict
372 self.oem_dicts = oem_dicts
373
374 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700375
Hongguang Chend7c160f2020-05-03 21:24:26 -0700376 # Skip _oem_props if oem_dicts is None to use BuildInfo in
377 # sign_target_files_apks
378 if self.oem_dicts:
379 self._oem_props = info_dict.get("oem_fingerprint_properties")
380 else:
381 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700382
Daniel Normand5fe8622020-01-08 17:01:11 -0800383 def check_fingerprint(fingerprint):
384 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
385 raise ValueError(
386 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
387 "3.2.2. Build Parameters.".format(fingerprint))
388
Daniel Normand5fe8622020-01-08 17:01:11 -0800389 self._partition_fingerprints = {}
390 for partition in PARTITIONS_WITH_CARE_MAP:
391 try:
392 fingerprint = self.CalculatePartitionFingerprint(partition)
393 check_fingerprint(fingerprint)
394 self._partition_fingerprints[partition] = fingerprint
395 except ExternalError:
396 continue
397 if "system" in self._partition_fingerprints:
398 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
399 # need a fingerprint when creating the image.
400 self._partition_fingerprints[
401 "system_other"] = self._partition_fingerprints["system"]
402
Tao Bao1c320f82019-10-04 23:25:12 -0700403 # These two should be computed only after setting self._oem_props.
404 self._device = self.GetOemProperty("ro.product.device")
405 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800406 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700407
408 @property
409 def is_ab(self):
410 return self._is_ab
411
412 @property
413 def device(self):
414 return self._device
415
416 @property
417 def fingerprint(self):
418 return self._fingerprint
419
420 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700421 def oem_props(self):
422 return self._oem_props
423
424 def __getitem__(self, key):
425 return self.info_dict[key]
426
427 def __setitem__(self, key, value):
428 self.info_dict[key] = value
429
430 def get(self, key, default=None):
431 return self.info_dict.get(key, default)
432
433 def items(self):
434 return self.info_dict.items()
435
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000436 def _GetRawBuildProp(self, prop, partition):
437 prop_file = '{}.build.prop'.format(
438 partition) if partition else 'build.prop'
439 partition_props = self.info_dict.get(prop_file)
440 if not partition_props:
441 return None
442 return partition_props.GetProp(prop)
443
Daniel Normand5fe8622020-01-08 17:01:11 -0800444 def GetPartitionBuildProp(self, prop, partition):
445 """Returns the inquired build property for the provided partition."""
446 # If provided a partition for this property, only look within that
447 # partition's build.prop.
448 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
449 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
450 else:
451 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000452
453 prop_val = self._GetRawBuildProp(prop, partition)
454 if prop_val is not None:
455 return prop_val
456 raise ExternalError("couldn't find %s in %s.build.prop" %
457 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800458
Tao Bao1c320f82019-10-04 23:25:12 -0700459 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800460 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700461 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
462 return self._ResolveRoProductBuildProp(prop)
463
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000464 prop_val = self._GetRawBuildProp(prop, None)
465 if prop_val is not None:
466 return prop_val
467
468 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700469
470 def _ResolveRoProductBuildProp(self, prop):
471 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000472 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700473 if prop_val:
474 return prop_val
475
Steven Laver8e2086e2020-04-27 16:26:31 -0700476 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000477 source_order_val = self._GetRawBuildProp(
478 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700479 if source_order_val:
480 source_order = source_order_val.split(",")
481 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700482 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700483
484 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700485 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700486 raise ExternalError(
487 "Invalid ro.product.property_source_order '{}'".format(source_order))
488
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700490 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 "ro.product", "ro.product.{}".format(source_partition), 1)
492 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700493 if prop_val:
494 return prop_val
495
496 raise ExternalError("couldn't resolve {}".format(prop))
497
Steven Laver8e2086e2020-04-27 16:26:31 -0700498 def _GetRoProductPropsDefaultSourceOrder(self):
499 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
500 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000501 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000503 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700504 if android_version == "10":
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
506 # NOTE: float() conversion of android_version will have rounding error.
507 # We are checking for "9" or less, and using "< 10" is well outside of
508 # possible floating point rounding.
509 try:
510 android_version_val = float(android_version)
511 except ValueError:
512 android_version_val = 0
513 if android_version_val < 10:
514 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
515 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
516
Tao Bao1c320f82019-10-04 23:25:12 -0700517 def GetOemProperty(self, key):
518 if self.oem_props is not None and key in self.oem_props:
519 return self.oem_dicts[0][key]
520 return self.GetBuildProp(key)
521
Daniel Normand5fe8622020-01-08 17:01:11 -0800522 def GetPartitionFingerprint(self, partition):
523 return self._partition_fingerprints.get(partition, None)
524
525 def CalculatePartitionFingerprint(self, partition):
526 try:
527 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
528 except ExternalError:
529 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
530 self.GetPartitionBuildProp("ro.product.brand", partition),
531 self.GetPartitionBuildProp("ro.product.name", partition),
532 self.GetPartitionBuildProp("ro.product.device", partition),
533 self.GetPartitionBuildProp("ro.build.version.release", partition),
534 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400535 self.GetPartitionBuildProp(
536 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800537 self.GetPartitionBuildProp("ro.build.type", partition),
538 self.GetPartitionBuildProp("ro.build.tags", partition))
539
Tao Bao1c320f82019-10-04 23:25:12 -0700540 def CalculateFingerprint(self):
541 if self.oem_props is None:
542 try:
543 return self.GetBuildProp("ro.build.fingerprint")
544 except ExternalError:
545 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
546 self.GetBuildProp("ro.product.brand"),
547 self.GetBuildProp("ro.product.name"),
548 self.GetBuildProp("ro.product.device"),
549 self.GetBuildProp("ro.build.version.release"),
550 self.GetBuildProp("ro.build.id"),
551 self.GetBuildProp("ro.build.version.incremental"),
552 self.GetBuildProp("ro.build.type"),
553 self.GetBuildProp("ro.build.tags"))
554 return "%s/%s/%s:%s" % (
555 self.GetOemProperty("ro.product.brand"),
556 self.GetOemProperty("ro.product.name"),
557 self.GetOemProperty("ro.product.device"),
558 self.GetBuildProp("ro.build.thumbprint"))
559
560 def WriteMountOemScript(self, script):
561 assert self.oem_props is not None
562 recovery_mount_options = self.info_dict.get("recovery_mount_options")
563 script.Mount("/oem", recovery_mount_options)
564
565 def WriteDeviceAssertions(self, script, oem_no_mount):
566 # Read the property directly if not using OEM properties.
567 if not self.oem_props:
568 script.AssertDevice(self.device)
569 return
570
571 # Otherwise assert OEM properties.
572 if not self.oem_dicts:
573 raise ExternalError(
574 "No OEM file provided to answer expected assertions")
575
576 for prop in self.oem_props.split():
577 values = []
578 for oem_dict in self.oem_dicts:
579 if prop in oem_dict:
580 values.append(oem_dict[prop])
581 if not values:
582 raise ExternalError(
583 "The OEM file is missing the property %s" % (prop,))
584 script.AssertOemProperty(prop, values, oem_no_mount)
585
586
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000587def ReadFromInputFile(input_file, fn):
588 """Reads the contents of fn from input zipfile or directory."""
589 if isinstance(input_file, zipfile.ZipFile):
590 return input_file.read(fn).decode()
591 else:
592 path = os.path.join(input_file, *fn.split("/"))
593 try:
594 with open(path) as f:
595 return f.read()
596 except IOError as e:
597 if e.errno == errno.ENOENT:
598 raise KeyError(fn)
599
600
Tao Bao410ad8b2018-08-24 12:08:38 -0700601def LoadInfoDict(input_file, repacking=False):
602 """Loads the key/value pairs from the given input target_files.
603
Tianjiea85bdf02020-07-29 11:56:19 -0700604 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700605 checks and returns the parsed key/value pairs for to the given build. It's
606 usually called early when working on input target_files files, e.g. when
607 generating OTAs, or signing builds. Note that the function may be called
608 against an old target_files file (i.e. from past dessert releases). So the
609 property parsing needs to be backward compatible.
610
611 In a `META/misc_info.txt`, a few properties are stored as links to the files
612 in the PRODUCT_OUT directory. It works fine with the build system. However,
613 they are no longer available when (re)generating images from target_files zip.
614 When `repacking` is True, redirect these properties to the actual files in the
615 unzipped directory.
616
617 Args:
618 input_file: The input target_files file, which could be an open
619 zipfile.ZipFile instance, or a str for the dir that contains the files
620 unzipped from a target_files file.
621 repacking: Whether it's trying repack an target_files file after loading the
622 info dict (default: False). If so, it will rewrite a few loaded
623 properties (e.g. selinux_fc, root_dir) to point to the actual files in
624 target_files file. When doing repacking, `input_file` must be a dir.
625
626 Returns:
627 A dict that contains the parsed key/value pairs.
628
629 Raises:
630 AssertionError: On invalid input arguments.
631 ValueError: On malformed input values.
632 """
633 if repacking:
634 assert isinstance(input_file, str), \
635 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700636
Doug Zongkerc9253822014-02-04 12:17:58 -0800637 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000638 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800639
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700640 try:
Michael Runge6e836112014-04-15 17:40:21 -0700641 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700642 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700643 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700644
Tao Bao410ad8b2018-08-24 12:08:38 -0700645 if "recovery_api_version" not in d:
646 raise ValueError("Failed to find 'recovery_api_version'")
647 if "fstab_version" not in d:
648 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800649
Tao Bao410ad8b2018-08-24 12:08:38 -0700650 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700651 # "selinux_fc" properties should point to the file_contexts files
652 # (file_contexts.bin) under META/.
653 for key in d:
654 if key.endswith("selinux_fc"):
655 fc_basename = os.path.basename(d[key])
656 fc_config = os.path.join(input_file, "META", fc_basename)
657 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700658
Daniel Norman72c626f2019-05-13 15:58:14 -0700659 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700660
Tom Cherryd14b8952018-08-09 14:26:00 -0700661 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700662 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700663 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700664 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700665
David Anderson0ec64ac2019-12-06 12:21:18 -0800666 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700667 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700668 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800669 key_name = part_name + "_base_fs_file"
670 if key_name not in d:
671 continue
672 basename = os.path.basename(d[key_name])
673 base_fs_file = os.path.join(input_file, "META", basename)
674 if os.path.exists(base_fs_file):
675 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700676 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700677 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800678 "Failed to find %s base fs file: %s", part_name, base_fs_file)
679 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700680
Doug Zongker37974732010-09-16 17:44:38 -0700681 def makeint(key):
682 if key in d:
683 d[key] = int(d[key], 0)
684
685 makeint("recovery_api_version")
686 makeint("blocksize")
687 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700688 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700689 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700690 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700691 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800692 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700693
Steve Muckle903a1ca2020-05-07 17:32:10 -0700694 boot_images = "boot.img"
695 if "boot_images" in d:
696 boot_images = d["boot_images"]
697 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400698 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700699
Tao Bao765668f2019-10-04 22:03:00 -0700700 # Load recovery fstab if applicable.
701 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800702
Tianjie Xu861f4132018-09-12 11:49:33 -0700703 # Tries to load the build props for all partitions with care_map, including
704 # system and vendor.
705 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800706 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000707 d[partition_prop] = PartitionBuildProps.FromInputFile(
708 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700709 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800710
Tao Bao3ed35d32019-10-07 20:48:48 -0700711 # Set up the salt (based on fingerprint) that will be used when adding AVB
712 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800713 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700714 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800715 for partition in PARTITIONS_WITH_CARE_MAP:
716 fingerprint = build_info.GetPartitionFingerprint(partition)
717 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400718 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800719
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700720 return d
721
Tao Baod1de6f32017-03-01 16:38:48 -0800722
Daniel Norman4cc9df62019-07-18 10:11:07 -0700723def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900724 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700725 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900726
Daniel Norman4cc9df62019-07-18 10:11:07 -0700727
728def LoadDictionaryFromFile(file_path):
729 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900730 return LoadDictionaryFromLines(lines)
731
732
Michael Runge6e836112014-04-15 17:40:21 -0700733def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700734 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700735 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700736 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700737 if not line or line.startswith("#"):
738 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700739 if "=" in line:
740 name, value = line.split("=", 1)
741 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700742 return d
743
Tao Baod1de6f32017-03-01 16:38:48 -0800744
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000745class PartitionBuildProps(object):
746 """The class holds the build prop of a particular partition.
747
748 This class loads the build.prop and holds the build properties for a given
749 partition. It also partially recognizes the 'import' statement in the
750 build.prop; and calculates alternative values of some specific build
751 properties during runtime.
752
753 Attributes:
754 input_file: a zipped target-file or an unzipped target-file directory.
755 partition: name of the partition.
756 props_allow_override: a list of build properties to search for the
757 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000758 build_props: a dict of build properties for the given partition.
759 prop_overrides: a set of props that are overridden by import.
760 placeholder_values: A dict of runtime variables' values to replace the
761 placeholders in the build.prop file. We expect exactly one value for
762 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000763 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400764
Tianjie Xu9afb2212020-05-10 21:48:15 +0000765 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000766 self.input_file = input_file
767 self.partition = name
768 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000769 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000770 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000771 self.prop_overrides = set()
772 self.placeholder_values = {}
773 if placeholder_values:
774 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000775
776 @staticmethod
777 def FromDictionary(name, build_props):
778 """Constructs an instance from a build prop dictionary."""
779
780 props = PartitionBuildProps("unknown", name)
781 props.build_props = build_props.copy()
782 return props
783
784 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000785 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000786 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000787 data = ''
788 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
789 '{}/build.prop'.format(name.upper())]:
790 try:
791 data = ReadFromInputFile(input_file, prop_file)
792 break
793 except KeyError:
794 logger.warning('Failed to read %s', prop_file)
795
Tianjie Xu9afb2212020-05-10 21:48:15 +0000796 props = PartitionBuildProps(input_file, name, placeholder_values)
797 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000798 return props
799
Tianjie Xu9afb2212020-05-10 21:48:15 +0000800 def _LoadBuildProp(self, data):
801 for line in data.split('\n'):
802 line = line.strip()
803 if not line or line.startswith("#"):
804 continue
805 if line.startswith("import"):
806 overrides = self._ImportParser(line)
807 duplicates = self.prop_overrides.intersection(overrides.keys())
808 if duplicates:
809 raise ValueError('prop {} is overridden multiple times'.format(
810 ','.join(duplicates)))
811 self.prop_overrides = self.prop_overrides.union(overrides.keys())
812 self.build_props.update(overrides)
813 elif "=" in line:
814 name, value = line.split("=", 1)
815 if name in self.prop_overrides:
816 raise ValueError('prop {} is set again after overridden by import '
817 'statement'.format(name))
818 self.build_props[name] = value
819
820 def _ImportParser(self, line):
821 """Parses the build prop in a given import statement."""
822
823 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400824 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000825 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700826
827 if len(tokens) == 3:
828 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
829 return {}
830
Tianjie Xu9afb2212020-05-10 21:48:15 +0000831 import_path = tokens[1]
832 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
833 raise ValueError('Unrecognized import path {}'.format(line))
834
835 # We only recognize a subset of import statement that the init process
836 # supports. And we can loose the restriction based on how the dynamic
837 # fingerprint is used in practice. The placeholder format should be
838 # ${placeholder}, and its value should be provided by the caller through
839 # the placeholder_values.
840 for prop, value in self.placeholder_values.items():
841 prop_place_holder = '${{{}}}'.format(prop)
842 if prop_place_holder in import_path:
843 import_path = import_path.replace(prop_place_holder, value)
844 if '$' in import_path:
845 logger.info('Unresolved place holder in import path %s', import_path)
846 return {}
847
848 import_path = import_path.replace('/{}'.format(self.partition),
849 self.partition.upper())
850 logger.info('Parsing build props override from %s', import_path)
851
852 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
853 d = LoadDictionaryFromLines(lines)
854 return {key: val for key, val in d.items()
855 if key in self.props_allow_override}
856
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000857 def GetProp(self, prop):
858 return self.build_props.get(prop)
859
860
Tianjie Xucfa86222016-03-07 16:31:19 -0800861def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
862 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700863 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700864 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700865 self.mount_point = mount_point
866 self.fs_type = fs_type
867 self.device = device
868 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700869 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700870 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700871
872 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800873 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700874 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700875 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700876 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700877
Tao Baod1de6f32017-03-01 16:38:48 -0800878 assert fstab_version == 2
879
880 d = {}
881 for line in data.split("\n"):
882 line = line.strip()
883 if not line or line.startswith("#"):
884 continue
885
886 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
887 pieces = line.split()
888 if len(pieces) != 5:
889 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
890
891 # Ignore entries that are managed by vold.
892 options = pieces[4]
893 if "voldmanaged=" in options:
894 continue
895
896 # It's a good line, parse it.
897 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700898 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800899 options = options.split(",")
900 for i in options:
901 if i.startswith("length="):
902 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700903 elif i == "slotselect":
904 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800905 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800906 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700907 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800908
Tao Baod1de6f32017-03-01 16:38:48 -0800909 mount_flags = pieces[3]
910 # Honor the SELinux context if present.
911 context = None
912 for i in mount_flags.split(","):
913 if i.startswith("context="):
914 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800915
Tao Baod1de6f32017-03-01 16:38:48 -0800916 mount_point = pieces[1]
917 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700918 device=pieces[0], length=length, context=context,
919 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800920
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700921 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700922 # system. Other areas assume system is always at "/system" so point /system
923 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700924 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800925 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700926 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700927 return d
928
929
Tao Bao765668f2019-10-04 22:03:00 -0700930def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
931 """Finds the path to recovery fstab and loads its contents."""
932 # recovery fstab is only meaningful when installing an update via recovery
933 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700934 if info_dict.get('ab_update') == 'true' and \
935 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700936 return None
937
938 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
939 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
940 # cases, since it may load the info_dict from an old build (e.g. when
941 # generating incremental OTAs from that build).
942 system_root_image = info_dict.get('system_root_image') == 'true'
943 if info_dict.get('no_recovery') != 'true':
944 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
945 if isinstance(input_file, zipfile.ZipFile):
946 if recovery_fstab_path not in input_file.namelist():
947 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
948 else:
949 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
950 if not os.path.exists(path):
951 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
952 return LoadRecoveryFSTab(
953 read_helper, info_dict['fstab_version'], recovery_fstab_path,
954 system_root_image)
955
956 if info_dict.get('recovery_as_boot') == 'true':
957 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
958 if isinstance(input_file, zipfile.ZipFile):
959 if recovery_fstab_path not in input_file.namelist():
960 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
961 else:
962 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
963 if not os.path.exists(path):
964 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
965 return LoadRecoveryFSTab(
966 read_helper, info_dict['fstab_version'], recovery_fstab_path,
967 system_root_image)
968
969 return None
970
971
Doug Zongker37974732010-09-16 17:44:38 -0700972def DumpInfoDict(d):
973 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700974 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700975
Dan Albert8b72aef2015-03-23 19:13:21 -0700976
Daniel Norman55417142019-11-25 16:04:36 -0800977def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700978 """Merges dynamic partition info variables.
979
980 Args:
981 framework_dict: The dictionary of dynamic partition info variables from the
982 partial framework target files.
983 vendor_dict: The dictionary of dynamic partition info variables from the
984 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700985
986 Returns:
987 The merged dynamic partition info dictionary.
988 """
989 merged_dict = {}
990 # Partition groups and group sizes are defined by the vendor dict because
991 # these values may vary for each board that uses a shared system image.
992 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800993 framework_dynamic_partition_list = framework_dict.get(
994 "dynamic_partition_list", "")
995 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
996 merged_dict["dynamic_partition_list"] = ("%s %s" % (
997 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700998 for partition_group in merged_dict["super_partition_groups"].split(" "):
999 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001000 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001001 if key not in vendor_dict:
1002 raise ValueError("Vendor dict does not contain required key %s." % key)
1003 merged_dict[key] = vendor_dict[key]
1004
1005 # Set the partition group's partition list using a concatenation of the
1006 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001007 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001008 merged_dict[key] = (
1009 "%s %s" %
1010 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301011
1012 # Pick virtual ab related flags from vendor dict, if defined.
1013 if "virtual_ab" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001014 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301015 if "virtual_ab_retrofit" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001016 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001017 return merged_dict
1018
1019
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001020def AppendAVBSigningArgs(cmd, partition):
1021 """Append signing arguments for avbtool."""
1022 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1023 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001024 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1025 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1026 if os.path.exists(new_key_path):
1027 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001028 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1029 if key_path and algorithm:
1030 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001031 avb_salt = OPTIONS.info_dict.get("avb_salt")
1032 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001033 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001034 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001035
1036
Tao Bao765668f2019-10-04 22:03:00 -07001037def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001038 """Returns the VBMeta arguments for partition.
1039
1040 It sets up the VBMeta argument by including the partition descriptor from the
1041 given 'image', or by configuring the partition as a chained partition.
1042
1043 Args:
1044 partition: The name of the partition (e.g. "system").
1045 image: The path to the partition image.
1046 info_dict: A dict returned by common.LoadInfoDict(). Will use
1047 OPTIONS.info_dict if None has been given.
1048
1049 Returns:
1050 A list of VBMeta arguments.
1051 """
1052 if info_dict is None:
1053 info_dict = OPTIONS.info_dict
1054
1055 # Check if chain partition is used.
1056 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001057 if not key_path:
1058 return ["--include_descriptors_from_image", image]
1059
1060 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1061 # into vbmeta.img. The recovery image will be configured on an independent
1062 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1063 # See details at
1064 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001065 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001066 return []
1067
1068 # Otherwise chain the partition into vbmeta.
1069 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1070 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001071
1072
Tao Bao02a08592018-07-22 12:40:45 -07001073def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1074 """Constructs and returns the arg to build or verify a chained partition.
1075
1076 Args:
1077 partition: The partition name.
1078 info_dict: The info dict to look up the key info and rollback index
1079 location.
1080 key: The key to be used for building or verifying the partition. Defaults to
1081 the key listed in info_dict.
1082
1083 Returns:
1084 A string of form "partition:rollback_index_location:key" that can be used to
1085 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001086 """
1087 if key is None:
1088 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001089 if key and not os.path.exists(key) and OPTIONS.search_path:
1090 new_key_path = os.path.join(OPTIONS.search_path, key)
1091 if os.path.exists(new_key_path):
1092 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001093 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001094 rollback_index_location = info_dict[
1095 "avb_" + partition + "_rollback_index_location"]
1096 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1097
1098
Tianjie20dd8f22020-04-19 15:51:16 -07001099def ConstructAftlMakeImageCommands(output_image):
1100 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001101
1102 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001103 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001104 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1105 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1106 'No AFTL manufacturer key provided.'
1107
1108 vbmeta_image = MakeTempFile()
1109 os.rename(output_image, vbmeta_image)
1110 build_info = BuildInfo(OPTIONS.info_dict)
1111 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001112 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001113 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001114 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001115 "--vbmeta_image_path", vbmeta_image,
1116 "--output", output_image,
1117 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001118 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001119 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1120 "--algorithm", "SHA256_RSA4096",
1121 "--padding", "4096"]
1122 if OPTIONS.aftl_signer_helper:
1123 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001124 return aftl_cmd
1125
1126
1127def AddAftlInclusionProof(output_image):
1128 """Appends the aftl inclusion proof to the vbmeta image."""
1129
1130 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001131 RunAndCheckOutput(aftl_cmd)
1132
1133 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1134 output_image, '--transparency_log_pub_keys',
1135 OPTIONS.aftl_key_path]
1136 RunAndCheckOutput(verify_cmd)
1137
1138
Daniel Norman276f0622019-07-26 14:13:51 -07001139def BuildVBMeta(image_path, partitions, name, needed_partitions):
1140 """Creates a VBMeta image.
1141
1142 It generates the requested VBMeta image. The requested image could be for
1143 top-level or chained VBMeta image, which is determined based on the name.
1144
1145 Args:
1146 image_path: The output path for the new VBMeta image.
1147 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001148 values. Only valid partition names are accepted, as partitions listed
1149 in common.AVB_PARTITIONS and custom partitions listed in
1150 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001151 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1152 needed_partitions: Partitions whose descriptors should be included into the
1153 generated VBMeta image.
1154
1155 Raises:
1156 AssertionError: On invalid input args.
1157 """
1158 avbtool = OPTIONS.info_dict["avb_avbtool"]
1159 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1160 AppendAVBSigningArgs(cmd, name)
1161
Hongguang Chenf23364d2020-04-27 18:36:36 -07001162 custom_partitions = OPTIONS.info_dict.get(
1163 "avb_custom_images_partition_list", "").strip().split()
1164
Daniel Norman276f0622019-07-26 14:13:51 -07001165 for partition, path in partitions.items():
1166 if partition not in needed_partitions:
1167 continue
1168 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001169 partition in AVB_VBMETA_PARTITIONS or
1170 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001171 'Unknown partition: {}'.format(partition)
1172 assert os.path.exists(path), \
1173 'Failed to find {} for {}'.format(path, partition)
1174 cmd.extend(GetAvbPartitionArg(partition, path))
1175
1176 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1177 if args and args.strip():
1178 split_args = shlex.split(args)
1179 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001180 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001181 # as a path relative to source tree, which may not be available at the
1182 # same location when running this script (we have the input target_files
1183 # zip only). For such cases, we additionally scan other locations (e.g.
1184 # IMAGES/, RADIO/, etc) before bailing out.
1185 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001186 chained_image = split_args[index + 1]
1187 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001188 continue
1189 found = False
1190 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1191 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001192 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001193 if os.path.exists(alt_path):
1194 split_args[index + 1] = alt_path
1195 found = True
1196 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001197 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001198 cmd.extend(split_args)
1199
1200 RunAndCheckOutput(cmd)
1201
Tianjie Xueaed60c2020-03-12 00:33:28 -07001202 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001203 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001204 AddAftlInclusionProof(image_path)
1205
Daniel Norman276f0622019-07-26 14:13:51 -07001206
J. Avila98cd4cc2020-06-10 20:09:10 +00001207def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001208 ramdisk_img = tempfile.NamedTemporaryFile()
1209
1210 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1211 cmd = ["mkbootfs", "-f", fs_config_file,
1212 os.path.join(sourcedir, "RAMDISK")]
1213 else:
1214 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1215 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001216 if lz4_ramdisks:
1217 p2 = Run(["lz4", "-l", "-12" , "--favor-decSpeed"], stdin=p1.stdout,
1218 stdout=ramdisk_img.file.fileno())
1219 else:
1220 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001221
1222 p2.wait()
1223 p1.wait()
1224 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001225 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001226
1227 return ramdisk_img
1228
1229
Steve Muckle9793cf62020-04-08 18:27:00 -07001230def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001231 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001232 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001233
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001234 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001235 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1236 we are building a two-step special image (i.e. building a recovery image to
1237 be loaded into /boot in two-step OTAs).
1238
1239 Return the image data, or None if sourcedir does not appear to contains files
1240 for building the requested image.
1241 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001242
Steve Muckle9793cf62020-04-08 18:27:00 -07001243 # "boot" or "recovery", without extension.
1244 partition_name = os.path.basename(sourcedir).lower()
1245
1246 if partition_name == "recovery":
1247 kernel = "kernel"
1248 else:
1249 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001250 kernel = kernel.replace(".img", "")
Steve Muckle9793cf62020-04-08 18:27:00 -07001251 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001252 return None
1253
1254 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001255 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001256
Doug Zongkerd5131602012-08-02 14:46:42 -07001257 if info_dict is None:
1258 info_dict = OPTIONS.info_dict
1259
Doug Zongkereef39442009-04-02 12:14:19 -07001260 img = tempfile.NamedTemporaryFile()
1261
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001262 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001263 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1264 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001265
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001266 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1267 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1268
Steve Muckle9793cf62020-04-08 18:27:00 -07001269 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001270
Benoit Fradina45a8682014-07-14 21:00:43 +02001271 fn = os.path.join(sourcedir, "second")
1272 if os.access(fn, os.F_OK):
1273 cmd.append("--second")
1274 cmd.append(fn)
1275
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001276 fn = os.path.join(sourcedir, "dtb")
1277 if os.access(fn, os.F_OK):
1278 cmd.append("--dtb")
1279 cmd.append(fn)
1280
Doug Zongker171f1cd2009-06-15 22:36:37 -07001281 fn = os.path.join(sourcedir, "cmdline")
1282 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001283 cmd.append("--cmdline")
1284 cmd.append(open(fn).read().rstrip("\n"))
1285
1286 fn = os.path.join(sourcedir, "base")
1287 if os.access(fn, os.F_OK):
1288 cmd.append("--base")
1289 cmd.append(open(fn).read().rstrip("\n"))
1290
Ying Wang4de6b5b2010-08-25 14:29:34 -07001291 fn = os.path.join(sourcedir, "pagesize")
1292 if os.access(fn, os.F_OK):
1293 cmd.append("--pagesize")
1294 cmd.append(open(fn).read().rstrip("\n"))
1295
Steve Mucklef84668e2020-03-16 19:13:46 -07001296 if partition_name == "recovery":
1297 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301298 if not args:
1299 # Fall back to "mkbootimg_args" for recovery image
1300 # in case "recovery_mkbootimg_args" is not set.
1301 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001302 else:
1303 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001304 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001305 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001306
Tao Bao76def242017-11-21 09:25:31 -08001307 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001308 if args and args.strip():
1309 cmd.extend(shlex.split(args))
1310
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001311 if has_ramdisk:
1312 cmd.extend(["--ramdisk", ramdisk_img.name])
1313
Tao Baod95e9fd2015-03-29 23:07:41 -07001314 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001315 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001316 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001317 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001318 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001319 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001320
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001321 if partition_name == "recovery":
1322 if info_dict.get("include_recovery_dtbo") == "true":
1323 fn = os.path.join(sourcedir, "recovery_dtbo")
1324 cmd.extend(["--recovery_dtbo", fn])
1325 if info_dict.get("include_recovery_acpio") == "true":
1326 fn = os.path.join(sourcedir, "recovery_acpio")
1327 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001328
Tao Bao986ee862018-10-04 15:46:16 -07001329 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001330
Tao Bao76def242017-11-21 09:25:31 -08001331 if (info_dict.get("boot_signer") == "true" and
1332 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001333 # Hard-code the path as "/boot" for two-step special recovery image (which
1334 # will be loaded into /boot during the two-step OTA).
1335 if two_step_image:
1336 path = "/boot"
1337 else:
Tao Baobf70c312017-07-11 17:27:55 -07001338 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001339 cmd = [OPTIONS.boot_signer_path]
1340 cmd.extend(OPTIONS.boot_signer_args)
1341 cmd.extend([path, img.name,
1342 info_dict["verity_key"] + ".pk8",
1343 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001344 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001345
Tao Baod95e9fd2015-03-29 23:07:41 -07001346 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001347 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001348 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001349 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001350 # We have switched from the prebuilt futility binary to using the tool
1351 # (futility-host) built from the source. Override the setting in the old
1352 # TF.zip.
1353 futility = info_dict["futility"]
1354 if futility.startswith("prebuilts/"):
1355 futility = "futility-host"
1356 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001357 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001358 info_dict["vboot_key"] + ".vbprivk",
1359 info_dict["vboot_subkey"] + ".vbprivk",
1360 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001361 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001362 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001363
Tao Baof3282b42015-04-01 11:21:55 -07001364 # Clean up the temp files.
1365 img_unsigned.close()
1366 img_keyblock.close()
1367
David Zeuthen8fecb282017-12-01 16:24:01 -05001368 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001369 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001370 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001371 if partition_name == "recovery":
1372 part_size = info_dict["recovery_size"]
1373 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001374 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001375 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001376 "--partition_size", str(part_size), "--partition_name",
1377 partition_name]
1378 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001379 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001380 if args and args.strip():
1381 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001382 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001383
1384 img.seek(os.SEEK_SET, 0)
1385 data = img.read()
1386
1387 if has_ramdisk:
1388 ramdisk_img.close()
1389 img.close()
1390
1391 return data
1392
1393
Doug Zongkerd5131602012-08-02 14:46:42 -07001394def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001395 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001396 """Return a File object with the desired bootable image.
1397
1398 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1399 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1400 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001401
Doug Zongker55d93282011-01-25 17:03:34 -08001402 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1403 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001404 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001405 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001406
1407 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1408 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001409 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001410 return File.FromLocalFile(name, prebuilt_path)
1411
Tao Bao32fcdab2018-10-12 10:30:39 -07001412 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001413
1414 if info_dict is None:
1415 info_dict = OPTIONS.info_dict
1416
1417 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001418 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1419 # for recovery.
1420 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1421 prebuilt_name != "boot.img" or
1422 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001423
Doug Zongker6f1d0312014-08-22 08:07:12 -07001424 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001425 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001426 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001427 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001428 if data:
1429 return File(name, data)
1430 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001431
Doug Zongkereef39442009-04-02 12:14:19 -07001432
Steve Mucklee1b10862019-07-10 10:49:37 -07001433def _BuildVendorBootImage(sourcedir, info_dict=None):
1434 """Build a vendor boot image from the specified sourcedir.
1435
1436 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1437 turn them into a vendor boot image.
1438
1439 Return the image data, or None if sourcedir does not appear to contains files
1440 for building the requested image.
1441 """
1442
1443 if info_dict is None:
1444 info_dict = OPTIONS.info_dict
1445
1446 img = tempfile.NamedTemporaryFile()
1447
J. Avila98cd4cc2020-06-10 20:09:10 +00001448 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1449 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001450
1451 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1452 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1453
1454 cmd = [mkbootimg]
1455
1456 fn = os.path.join(sourcedir, "dtb")
1457 if os.access(fn, os.F_OK):
1458 cmd.append("--dtb")
1459 cmd.append(fn)
1460
1461 fn = os.path.join(sourcedir, "vendor_cmdline")
1462 if os.access(fn, os.F_OK):
1463 cmd.append("--vendor_cmdline")
1464 cmd.append(open(fn).read().rstrip("\n"))
1465
1466 fn = os.path.join(sourcedir, "base")
1467 if os.access(fn, os.F_OK):
1468 cmd.append("--base")
1469 cmd.append(open(fn).read().rstrip("\n"))
1470
1471 fn = os.path.join(sourcedir, "pagesize")
1472 if os.access(fn, os.F_OK):
1473 cmd.append("--pagesize")
1474 cmd.append(open(fn).read().rstrip("\n"))
1475
1476 args = info_dict.get("mkbootimg_args")
1477 if args and args.strip():
1478 cmd.extend(shlex.split(args))
1479
1480 args = info_dict.get("mkbootimg_version_args")
1481 if args and args.strip():
1482 cmd.extend(shlex.split(args))
1483
1484 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1485 cmd.extend(["--vendor_boot", img.name])
1486
1487 RunAndCheckOutput(cmd)
1488
1489 # AVB: if enabled, calculate and add hash.
1490 if info_dict.get("avb_enable") == "true":
1491 avbtool = info_dict["avb_avbtool"]
1492 part_size = info_dict["vendor_boot_size"]
1493 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001494 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001495 AppendAVBSigningArgs(cmd, "vendor_boot")
1496 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1497 if args and args.strip():
1498 cmd.extend(shlex.split(args))
1499 RunAndCheckOutput(cmd)
1500
1501 img.seek(os.SEEK_SET, 0)
1502 data = img.read()
1503
1504 ramdisk_img.close()
1505 img.close()
1506
1507 return data
1508
1509
1510def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1511 info_dict=None):
1512 """Return a File object with the desired vendor boot image.
1513
1514 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1515 the source files in 'unpack_dir'/'tree_subdir'."""
1516
1517 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1518 if os.path.exists(prebuilt_path):
1519 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1520 return File.FromLocalFile(name, prebuilt_path)
1521
1522 logger.info("building image from target_files %s...", tree_subdir)
1523
1524 if info_dict is None:
1525 info_dict = OPTIONS.info_dict
1526
Kelvin Zhang0876c412020-06-23 15:06:58 -04001527 data = _BuildVendorBootImage(
1528 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001529 if data:
1530 return File(name, data)
1531 return None
1532
1533
Narayan Kamatha07bf042017-08-14 14:49:21 +01001534def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001535 """Gunzips the given gzip compressed file to a given output file."""
1536 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001537 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001538 shutil.copyfileobj(in_file, out_file)
1539
1540
Tao Bao0ff15de2019-03-20 11:26:06 -07001541def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001542 """Unzips the archive to the given directory.
1543
1544 Args:
1545 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001546 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001547 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1548 archvie. Non-matching patterns will be filtered out. If there's no match
1549 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001550 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001551 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001552 if patterns is not None:
1553 # Filter out non-matching patterns. unzip will complain otherwise.
1554 with zipfile.ZipFile(filename) as input_zip:
1555 names = input_zip.namelist()
1556 filtered = [
1557 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1558
1559 # There isn't any matching files. Don't unzip anything.
1560 if not filtered:
1561 return
1562 cmd.extend(filtered)
1563
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001564 RunAndCheckOutput(cmd)
1565
1566
Doug Zongker75f17362009-12-08 13:46:44 -08001567def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001568 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001569
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001570 Args:
1571 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1572 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1573
1574 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1575 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001576
Tao Bao1c830bf2017-12-25 10:43:47 -08001577 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001578 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001579 """
Doug Zongkereef39442009-04-02 12:14:19 -07001580
Tao Bao1c830bf2017-12-25 10:43:47 -08001581 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001582 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1583 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001584 UnzipToDir(m.group(1), tmp, pattern)
1585 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001586 filename = m.group(1)
1587 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001588 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001589
Tao Baodba59ee2018-01-09 13:21:02 -08001590 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001591
1592
Yifan Hong8a66a712019-04-04 15:37:57 -07001593def GetUserImage(which, tmpdir, input_zip,
1594 info_dict=None,
1595 allow_shared_blocks=None,
1596 hashtree_info_generator=None,
1597 reset_file_map=False):
1598 """Returns an Image object suitable for passing to BlockImageDiff.
1599
1600 This function loads the specified image from the given path. If the specified
1601 image is sparse, it also performs additional processing for OTA purpose. For
1602 example, it always adds block 0 to clobbered blocks list. It also detects
1603 files that cannot be reconstructed from the block list, for whom we should
1604 avoid applying imgdiff.
1605
1606 Args:
1607 which: The partition name.
1608 tmpdir: The directory that contains the prebuilt image and block map file.
1609 input_zip: The target-files ZIP archive.
1610 info_dict: The dict to be looked up for relevant info.
1611 allow_shared_blocks: If image is sparse, whether having shared blocks is
1612 allowed. If none, it is looked up from info_dict.
1613 hashtree_info_generator: If present and image is sparse, generates the
1614 hashtree_info for this sparse image.
1615 reset_file_map: If true and image is sparse, reset file map before returning
1616 the image.
1617 Returns:
1618 A Image object. If it is a sparse image and reset_file_map is False, the
1619 image will have file_map info loaded.
1620 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001621 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001622 info_dict = LoadInfoDict(input_zip)
1623
1624 is_sparse = info_dict.get("extfs_sparse_flag")
1625
1626 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1627 # shared blocks (i.e. some blocks will show up in multiple files' block
1628 # list). We can only allocate such shared blocks to the first "owner", and
1629 # disable imgdiff for all later occurrences.
1630 if allow_shared_blocks is None:
1631 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1632
1633 if is_sparse:
1634 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1635 hashtree_info_generator)
1636 if reset_file_map:
1637 img.ResetFileMap()
1638 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001639 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001640
1641
1642def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1643 """Returns a Image object suitable for passing to BlockImageDiff.
1644
1645 This function loads the specified non-sparse image from the given path.
1646
1647 Args:
1648 which: The partition name.
1649 tmpdir: The directory that contains the prebuilt image and block map file.
1650 Returns:
1651 A Image object.
1652 """
1653 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1654 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1655
1656 # The image and map files must have been created prior to calling
1657 # ota_from_target_files.py (since LMP).
1658 assert os.path.exists(path) and os.path.exists(mappath)
1659
Tianjie Xu41976c72019-07-03 13:57:01 -07001660 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1661
Yifan Hong8a66a712019-04-04 15:37:57 -07001662
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001663def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1664 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001665 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1666
1667 This function loads the specified sparse image from the given path, and
1668 performs additional processing for OTA purpose. For example, it always adds
1669 block 0 to clobbered blocks list. It also detects files that cannot be
1670 reconstructed from the block list, for whom we should avoid applying imgdiff.
1671
1672 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001673 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001674 tmpdir: The directory that contains the prebuilt image and block map file.
1675 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001676 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001677 hashtree_info_generator: If present, generates the hashtree_info for this
1678 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001679 Returns:
1680 A SparseImage object, with file_map info loaded.
1681 """
Tao Baoc765cca2018-01-31 17:32:40 -08001682 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1683 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1684
1685 # The image and map files must have been created prior to calling
1686 # ota_from_target_files.py (since LMP).
1687 assert os.path.exists(path) and os.path.exists(mappath)
1688
1689 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1690 # it to clobbered_blocks so that it will be written to the target
1691 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1692 clobbered_blocks = "0"
1693
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001694 image = sparse_img.SparseImage(
1695 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1696 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001697
1698 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1699 # if they contain all zeros. We can't reconstruct such a file from its block
1700 # list. Tag such entries accordingly. (Bug: 65213616)
1701 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001702 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001703 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001704 continue
1705
Tom Cherryd14b8952018-08-09 14:26:00 -07001706 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1707 # filename listed in system.map may contain an additional leading slash
1708 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1709 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001710 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001711
Tom Cherryd14b8952018-08-09 14:26:00 -07001712 # Special handling another case, where files not under /system
1713 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001714 if which == 'system' and not arcname.startswith('SYSTEM'):
1715 arcname = 'ROOT/' + arcname
1716
1717 assert arcname in input_zip.namelist(), \
1718 "Failed to find the ZIP entry for {}".format(entry)
1719
Tao Baoc765cca2018-01-31 17:32:40 -08001720 info = input_zip.getinfo(arcname)
1721 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001722
1723 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001724 # image, check the original block list to determine its completeness. Note
1725 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001726 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001727 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001728
Tao Baoc765cca2018-01-31 17:32:40 -08001729 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1730 ranges.extra['incomplete'] = True
1731
1732 return image
1733
1734
Doug Zongkereef39442009-04-02 12:14:19 -07001735def GetKeyPasswords(keylist):
1736 """Given a list of keys, prompt the user to enter passwords for
1737 those which require them. Return a {key: password} dict. password
1738 will be None if the key has no password."""
1739
Doug Zongker8ce7c252009-05-22 13:34:54 -07001740 no_passwords = []
1741 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001742 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001743 devnull = open("/dev/null", "w+b")
1744 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001745 # We don't need a password for things that aren't really keys.
1746 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001747 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001748 continue
1749
T.R. Fullhart37e10522013-03-18 10:31:26 -07001750 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001751 "-inform", "DER", "-nocrypt"],
1752 stdin=devnull.fileno(),
1753 stdout=devnull.fileno(),
1754 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001755 p.communicate()
1756 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001757 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001758 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001759 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001760 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1761 "-inform", "DER", "-passin", "pass:"],
1762 stdin=devnull.fileno(),
1763 stdout=devnull.fileno(),
1764 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001765 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001766 if p.returncode == 0:
1767 # Encrypted key with empty string as password.
1768 key_passwords[k] = ''
1769 elif stderr.startswith('Error decrypting key'):
1770 # Definitely encrypted key.
1771 # It would have said "Error reading key" if it didn't parse correctly.
1772 need_passwords.append(k)
1773 else:
1774 # Potentially, a type of key that openssl doesn't understand.
1775 # We'll let the routines in signapk.jar handle it.
1776 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001777 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001778
T.R. Fullhart37e10522013-03-18 10:31:26 -07001779 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001780 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001781 return key_passwords
1782
1783
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001784def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001785 """Gets the minSdkVersion declared in the APK.
1786
changho.shin0f125362019-07-08 10:59:00 +09001787 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001788 This can be both a decimal number (API Level) or a codename.
1789
1790 Args:
1791 apk_name: The APK filename.
1792
1793 Returns:
1794 The parsed SDK version string.
1795
1796 Raises:
1797 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001798 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001799 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001800 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001801 stderr=subprocess.PIPE)
1802 stdoutdata, stderrdata = proc.communicate()
1803 if proc.returncode != 0:
1804 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001805 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001806 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001807
Tao Baof47bf0f2018-03-21 23:28:51 -07001808 for line in stdoutdata.split("\n"):
1809 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001810 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1811 if m:
1812 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001813 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001814
1815
1816def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001817 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001818
Tao Baof47bf0f2018-03-21 23:28:51 -07001819 If minSdkVersion is set to a codename, it is translated to a number using the
1820 provided map.
1821
1822 Args:
1823 apk_name: The APK filename.
1824
1825 Returns:
1826 The parsed SDK version number.
1827
1828 Raises:
1829 ExternalError: On failing to get the min SDK version number.
1830 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001831 version = GetMinSdkVersion(apk_name)
1832 try:
1833 return int(version)
1834 except ValueError:
1835 # Not a decimal number. Codename?
1836 if version in codename_to_api_level_map:
1837 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001838 raise ExternalError(
1839 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1840 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001841
1842
1843def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001844 codename_to_api_level_map=None, whole_file=False,
1845 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001846 """Sign the input_name zip/jar/apk, producing output_name. Use the
1847 given key and password (the latter may be None if the key does not
1848 have a password.
1849
Doug Zongker951495f2009-08-14 12:44:19 -07001850 If whole_file is true, use the "-w" option to SignApk to embed a
1851 signature that covers the whole file in the archive comment of the
1852 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001853
1854 min_api_level is the API Level (int) of the oldest platform this file may end
1855 up on. If not specified for an APK, the API Level is obtained by interpreting
1856 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1857
1858 codename_to_api_level_map is needed to translate the codename which may be
1859 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001860
1861 Caller may optionally specify extra args to be passed to SignApk, which
1862 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001863 """
Tao Bao76def242017-11-21 09:25:31 -08001864 if codename_to_api_level_map is None:
1865 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001866 if extra_signapk_args is None:
1867 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001868
Alex Klyubin9667b182015-12-10 13:38:50 -08001869 java_library_path = os.path.join(
1870 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1871
Tao Baoe95540e2016-11-08 12:08:53 -08001872 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1873 ["-Djava.library.path=" + java_library_path,
1874 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001875 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001876 if whole_file:
1877 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001878
1879 min_sdk_version = min_api_level
1880 if min_sdk_version is None:
1881 if not whole_file:
1882 min_sdk_version = GetMinSdkVersionInt(
1883 input_name, codename_to_api_level_map)
1884 if min_sdk_version is not None:
1885 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1886
T.R. Fullhart37e10522013-03-18 10:31:26 -07001887 cmd.extend([key + OPTIONS.public_key_suffix,
1888 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001889 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001890
Tao Bao73dd4f42018-10-04 16:25:33 -07001891 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001892 if password is not None:
1893 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001894 stdoutdata, _ = proc.communicate(password)
1895 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001896 raise ExternalError(
1897 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001898 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001899
Doug Zongkereef39442009-04-02 12:14:19 -07001900
Doug Zongker37974732010-09-16 17:44:38 -07001901def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001902 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001903
Tao Bao9dd909e2017-11-14 11:27:32 -08001904 For non-AVB images, raise exception if the data is too big. Print a warning
1905 if the data is nearing the maximum size.
1906
1907 For AVB images, the actual image size should be identical to the limit.
1908
1909 Args:
1910 data: A string that contains all the data for the partition.
1911 target: The partition name. The ".img" suffix is optional.
1912 info_dict: The dict to be looked up for relevant info.
1913 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001914 if target.endswith(".img"):
1915 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001916 mount_point = "/" + target
1917
Ying Wangf8824af2014-06-03 14:07:27 -07001918 fs_type = None
1919 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001920 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001921 if mount_point == "/userdata":
1922 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001923 p = info_dict["fstab"][mount_point]
1924 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001925 device = p.device
1926 if "/" in device:
1927 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001928 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001929 if not fs_type or not limit:
1930 return
Doug Zongkereef39442009-04-02 12:14:19 -07001931
Andrew Boie0f9aec82012-02-14 09:32:52 -08001932 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001933 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1934 # path.
1935 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1936 if size != limit:
1937 raise ExternalError(
1938 "Mismatching image size for %s: expected %d actual %d" % (
1939 target, limit, size))
1940 else:
1941 pct = float(size) * 100.0 / limit
1942 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1943 if pct >= 99.0:
1944 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001945
1946 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001947 logger.warning("\n WARNING: %s\n", msg)
1948 else:
1949 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001950
1951
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001952def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001953 """Parses the APK certs info from a given target-files zip.
1954
1955 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1956 tuple with the following elements: (1) a dictionary that maps packages to
1957 certs (based on the "certificate" and "private_key" attributes in the file;
1958 (2) a string representing the extension of compressed APKs in the target files
1959 (e.g ".gz", ".bro").
1960
1961 Args:
1962 tf_zip: The input target_files ZipFile (already open).
1963
1964 Returns:
1965 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1966 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1967 no compressed APKs.
1968 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001969 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001970 compressed_extension = None
1971
Tao Bao0f990332017-09-08 19:02:54 -07001972 # META/apkcerts.txt contains the info for _all_ the packages known at build
1973 # time. Filter out the ones that are not installed.
1974 installed_files = set()
1975 for name in tf_zip.namelist():
1976 basename = os.path.basename(name)
1977 if basename:
1978 installed_files.add(basename)
1979
Tao Baoda30cfa2017-12-01 16:19:46 -08001980 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001981 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001982 if not line:
1983 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001984 m = re.match(
1985 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001986 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1987 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001988 line)
1989 if not m:
1990 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001991
Tao Bao818ddf52018-01-05 11:17:34 -08001992 matches = m.groupdict()
1993 cert = matches["CERT"]
1994 privkey = matches["PRIVKEY"]
1995 name = matches["NAME"]
1996 this_compressed_extension = matches["COMPRESSED"]
1997
1998 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1999 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2000 if cert in SPECIAL_CERT_STRINGS and not privkey:
2001 certmap[name] = cert
2002 elif (cert.endswith(OPTIONS.public_key_suffix) and
2003 privkey.endswith(OPTIONS.private_key_suffix) and
2004 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2005 certmap[name] = cert[:-public_key_suffix_len]
2006 else:
2007 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2008
2009 if not this_compressed_extension:
2010 continue
2011
2012 # Only count the installed files.
2013 filename = name + '.' + this_compressed_extension
2014 if filename not in installed_files:
2015 continue
2016
2017 # Make sure that all the values in the compression map have the same
2018 # extension. We don't support multiple compression methods in the same
2019 # system image.
2020 if compressed_extension:
2021 if this_compressed_extension != compressed_extension:
2022 raise ValueError(
2023 "Multiple compressed extensions: {} vs {}".format(
2024 compressed_extension, this_compressed_extension))
2025 else:
2026 compressed_extension = this_compressed_extension
2027
2028 return (certmap,
2029 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002030
2031
Doug Zongkereef39442009-04-02 12:14:19 -07002032COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002033Global options
2034
2035 -p (--path) <dir>
2036 Prepend <dir>/bin to the list of places to search for binaries run by this
2037 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002038
Doug Zongker05d3dea2009-06-22 11:32:31 -07002039 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002040 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002041
Tao Bao30df8b42018-04-23 15:32:53 -07002042 -x (--extra) <key=value>
2043 Add a key/value pair to the 'extras' dict, which device-specific extension
2044 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002045
Doug Zongkereef39442009-04-02 12:14:19 -07002046 -v (--verbose)
2047 Show command lines being executed.
2048
2049 -h (--help)
2050 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002051
2052 --logfile <file>
2053 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002054"""
2055
Kelvin Zhang0876c412020-06-23 15:06:58 -04002056
Doug Zongkereef39442009-04-02 12:14:19 -07002057def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002058 print(docstring.rstrip("\n"))
2059 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002060
2061
2062def ParseOptions(argv,
2063 docstring,
2064 extra_opts="", extra_long_opts=(),
2065 extra_option_handler=None):
2066 """Parse the options in argv and return any arguments that aren't
2067 flags. docstring is the calling module's docstring, to be displayed
2068 for errors and -h. extra_opts and extra_long_opts are for flags
2069 defined by the caller, which are processed by passing them to
2070 extra_option_handler."""
2071
2072 try:
2073 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002074 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002075 ["help", "verbose", "path=", "signapk_path=",
2076 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002077 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002078 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2079 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002080 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2081 "aftl_key_path=", "aftl_manufacturer_key_path=",
2082 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002083 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002084 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002085 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002086 sys.exit(2)
2087
Doug Zongkereef39442009-04-02 12:14:19 -07002088 for o, a in opts:
2089 if o in ("-h", "--help"):
2090 Usage(docstring)
2091 sys.exit()
2092 elif o in ("-v", "--verbose"):
2093 OPTIONS.verbose = True
2094 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002095 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002096 elif o in ("--signapk_path",):
2097 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002098 elif o in ("--signapk_shared_library_path",):
2099 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002100 elif o in ("--extra_signapk_args",):
2101 OPTIONS.extra_signapk_args = shlex.split(a)
2102 elif o in ("--java_path",):
2103 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002104 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002105 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002106 elif o in ("--android_jar_path",):
2107 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002108 elif o in ("--public_key_suffix",):
2109 OPTIONS.public_key_suffix = a
2110 elif o in ("--private_key_suffix",):
2111 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002112 elif o in ("--boot_signer_path",):
2113 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002114 elif o in ("--boot_signer_args",):
2115 OPTIONS.boot_signer_args = shlex.split(a)
2116 elif o in ("--verity_signer_path",):
2117 OPTIONS.verity_signer_path = a
2118 elif o in ("--verity_signer_args",):
2119 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002120 elif o in ("--aftl_tool_path",):
2121 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002122 elif o in ("--aftl_server",):
2123 OPTIONS.aftl_server = a
2124 elif o in ("--aftl_key_path",):
2125 OPTIONS.aftl_key_path = a
2126 elif o in ("--aftl_manufacturer_key_path",):
2127 OPTIONS.aftl_manufacturer_key_path = a
2128 elif o in ("--aftl_signer_helper",):
2129 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002130 elif o in ("-s", "--device_specific"):
2131 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002132 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002133 key, value = a.split("=", 1)
2134 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002135 elif o in ("--logfile",):
2136 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002137 else:
2138 if extra_option_handler is None or not extra_option_handler(o, a):
2139 assert False, "unknown option \"%s\"" % (o,)
2140
Doug Zongker85448772014-09-09 14:59:20 -07002141 if OPTIONS.search_path:
2142 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2143 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002144
2145 return args
2146
2147
Tao Bao4c851b12016-09-19 13:54:38 -07002148def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002149 """Make a temp file and add it to the list of things to be deleted
2150 when Cleanup() is called. Return the filename."""
2151 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2152 os.close(fd)
2153 OPTIONS.tempfiles.append(fn)
2154 return fn
2155
2156
Tao Bao1c830bf2017-12-25 10:43:47 -08002157def MakeTempDir(prefix='tmp', suffix=''):
2158 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2159
2160 Returns:
2161 The absolute pathname of the new directory.
2162 """
2163 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2164 OPTIONS.tempfiles.append(dir_name)
2165 return dir_name
2166
2167
Doug Zongkereef39442009-04-02 12:14:19 -07002168def Cleanup():
2169 for i in OPTIONS.tempfiles:
2170 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002171 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002172 else:
2173 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002174 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002175
2176
2177class PasswordManager(object):
2178 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002179 self.editor = os.getenv("EDITOR")
2180 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002181
2182 def GetPasswords(self, items):
2183 """Get passwords corresponding to each string in 'items',
2184 returning a dict. (The dict may have keys in addition to the
2185 values in 'items'.)
2186
2187 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2188 user edit that file to add more needed passwords. If no editor is
2189 available, or $ANDROID_PW_FILE isn't define, prompts the user
2190 interactively in the ordinary way.
2191 """
2192
2193 current = self.ReadFile()
2194
2195 first = True
2196 while True:
2197 missing = []
2198 for i in items:
2199 if i not in current or not current[i]:
2200 missing.append(i)
2201 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002202 if not missing:
2203 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002204
2205 for i in missing:
2206 current[i] = ""
2207
2208 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002209 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002210 if sys.version_info[0] >= 3:
2211 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002212 answer = raw_input("try to edit again? [y]> ").strip()
2213 if answer and answer[0] not in 'yY':
2214 raise RuntimeError("key passwords unavailable")
2215 first = False
2216
2217 current = self.UpdateAndReadFile(current)
2218
Kelvin Zhang0876c412020-06-23 15:06:58 -04002219 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002220 """Prompt the user to enter a value (password) for each key in
2221 'current' whose value is fales. Returns a new dict with all the
2222 values.
2223 """
2224 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002225 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002226 if v:
2227 result[k] = v
2228 else:
2229 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002230 result[k] = getpass.getpass(
2231 "Enter password for %s key> " % k).strip()
2232 if result[k]:
2233 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002234 return result
2235
2236 def UpdateAndReadFile(self, current):
2237 if not self.editor or not self.pwfile:
2238 return self.PromptResult(current)
2239
2240 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002241 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002242 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2243 f.write("# (Additional spaces are harmless.)\n\n")
2244
2245 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002246 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002247 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002248 f.write("[[[ %s ]]] %s\n" % (v, k))
2249 if not v and first_line is None:
2250 # position cursor on first line with no password.
2251 first_line = i + 4
2252 f.close()
2253
Tao Bao986ee862018-10-04 15:46:16 -07002254 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255
2256 return self.ReadFile()
2257
2258 def ReadFile(self):
2259 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002260 if self.pwfile is None:
2261 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002262 try:
2263 f = open(self.pwfile, "r")
2264 for line in f:
2265 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002266 if not line or line[0] == '#':
2267 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002268 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2269 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002270 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002271 else:
2272 result[m.group(2)] = m.group(1)
2273 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002274 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002275 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002276 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002277 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002278
2279
Dan Albert8e0178d2015-01-27 15:53:15 -08002280def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2281 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002282
2283 # http://b/18015246
2284 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2285 # for files larger than 2GiB. We can work around this by adjusting their
2286 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2287 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2288 # it isn't clear to me exactly what circumstances cause this).
2289 # `zipfile.write()` must be used directly to work around this.
2290 #
2291 # This mess can be avoided if we port to python3.
2292 saved_zip64_limit = zipfile.ZIP64_LIMIT
2293 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2294
2295 if compress_type is None:
2296 compress_type = zip_file.compression
2297 if arcname is None:
2298 arcname = filename
2299
2300 saved_stat = os.stat(filename)
2301
2302 try:
2303 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2304 # file to be zipped and reset it when we're done.
2305 os.chmod(filename, perms)
2306
2307 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002308 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2309 # intentional. zip stores datetimes in local time without a time zone
2310 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2311 # in the zip archive.
2312 local_epoch = datetime.datetime.fromtimestamp(0)
2313 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002314 os.utime(filename, (timestamp, timestamp))
2315
2316 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2317 finally:
2318 os.chmod(filename, saved_stat.st_mode)
2319 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2320 zipfile.ZIP64_LIMIT = saved_zip64_limit
2321
2322
Tao Bao58c1b962015-05-20 09:32:18 -07002323def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002324 compress_type=None):
2325 """Wrap zipfile.writestr() function to work around the zip64 limit.
2326
2327 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2328 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2329 when calling crc32(bytes).
2330
2331 But it still works fine to write a shorter string into a large zip file.
2332 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2333 when we know the string won't be too long.
2334 """
2335
2336 saved_zip64_limit = zipfile.ZIP64_LIMIT
2337 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2338
2339 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2340 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002341 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002342 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002343 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002344 else:
Tao Baof3282b42015-04-01 11:21:55 -07002345 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002346 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2347 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2348 # such a case (since
2349 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2350 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2351 # permission bits. We follow the logic in Python 3 to get consistent
2352 # behavior between using the two versions.
2353 if not zinfo.external_attr:
2354 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002355
2356 # If compress_type is given, it overrides the value in zinfo.
2357 if compress_type is not None:
2358 zinfo.compress_type = compress_type
2359
Tao Bao58c1b962015-05-20 09:32:18 -07002360 # If perms is given, it has a priority.
2361 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002362 # If perms doesn't set the file type, mark it as a regular file.
2363 if perms & 0o770000 == 0:
2364 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002365 zinfo.external_attr = perms << 16
2366
Tao Baof3282b42015-04-01 11:21:55 -07002367 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002368 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2369
Dan Albert8b72aef2015-03-23 19:13:21 -07002370 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002371 zipfile.ZIP64_LIMIT = saved_zip64_limit
2372
2373
Tao Bao89d7ab22017-12-14 17:05:33 -08002374def ZipDelete(zip_filename, entries):
2375 """Deletes entries from a ZIP file.
2376
2377 Since deleting entries from a ZIP file is not supported, it shells out to
2378 'zip -d'.
2379
2380 Args:
2381 zip_filename: The name of the ZIP file.
2382 entries: The name of the entry, or the list of names to be deleted.
2383
2384 Raises:
2385 AssertionError: In case of non-zero return from 'zip'.
2386 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002387 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002388 entries = [entries]
2389 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002390 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002391
2392
Tao Baof3282b42015-04-01 11:21:55 -07002393def ZipClose(zip_file):
2394 # http://b/18015246
2395 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2396 # central directory.
2397 saved_zip64_limit = zipfile.ZIP64_LIMIT
2398 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2399
2400 zip_file.close()
2401
2402 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002403
2404
2405class DeviceSpecificParams(object):
2406 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002407
Doug Zongker05d3dea2009-06-22 11:32:31 -07002408 def __init__(self, **kwargs):
2409 """Keyword arguments to the constructor become attributes of this
2410 object, which is passed to all functions in the device-specific
2411 module."""
Tao Bao38884282019-07-10 22:20:56 -07002412 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002413 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002414 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002415
2416 if self.module is None:
2417 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002418 if not path:
2419 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002420 try:
2421 if os.path.isdir(path):
2422 info = imp.find_module("releasetools", [path])
2423 else:
2424 d, f = os.path.split(path)
2425 b, x = os.path.splitext(f)
2426 if x == ".py":
2427 f = b
2428 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002429 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002430 self.module = imp.load_module("device_specific", *info)
2431 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002432 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002433
2434 def _DoCall(self, function_name, *args, **kwargs):
2435 """Call the named function in the device-specific module, passing
2436 the given args and kwargs. The first argument to the call will be
2437 the DeviceSpecific object itself. If there is no module, or the
2438 module does not define the function, return the value of the
2439 'default' kwarg (which itself defaults to None)."""
2440 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002441 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002442 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2443
2444 def FullOTA_Assertions(self):
2445 """Called after emitting the block of assertions at the top of a
2446 full OTA package. Implementations can add whatever additional
2447 assertions they like."""
2448 return self._DoCall("FullOTA_Assertions")
2449
Doug Zongkere5ff5902012-01-17 10:55:37 -08002450 def FullOTA_InstallBegin(self):
2451 """Called at the start of full OTA installation."""
2452 return self._DoCall("FullOTA_InstallBegin")
2453
Yifan Hong10c530d2018-12-27 17:34:18 -08002454 def FullOTA_GetBlockDifferences(self):
2455 """Called during full OTA installation and verification.
2456 Implementation should return a list of BlockDifference objects describing
2457 the update on each additional partitions.
2458 """
2459 return self._DoCall("FullOTA_GetBlockDifferences")
2460
Doug Zongker05d3dea2009-06-22 11:32:31 -07002461 def FullOTA_InstallEnd(self):
2462 """Called at the end of full OTA installation; typically this is
2463 used to install the image for the device's baseband processor."""
2464 return self._DoCall("FullOTA_InstallEnd")
2465
2466 def IncrementalOTA_Assertions(self):
2467 """Called after emitting the block of assertions at the top of an
2468 incremental OTA package. Implementations can add whatever
2469 additional assertions they like."""
2470 return self._DoCall("IncrementalOTA_Assertions")
2471
Doug Zongkere5ff5902012-01-17 10:55:37 -08002472 def IncrementalOTA_VerifyBegin(self):
2473 """Called at the start of the verification phase of incremental
2474 OTA installation; additional checks can be placed here to abort
2475 the script before any changes are made."""
2476 return self._DoCall("IncrementalOTA_VerifyBegin")
2477
Doug Zongker05d3dea2009-06-22 11:32:31 -07002478 def IncrementalOTA_VerifyEnd(self):
2479 """Called at the end of the verification phase of incremental OTA
2480 installation; additional checks can be placed here to abort the
2481 script before any changes are made."""
2482 return self._DoCall("IncrementalOTA_VerifyEnd")
2483
Doug Zongkere5ff5902012-01-17 10:55:37 -08002484 def IncrementalOTA_InstallBegin(self):
2485 """Called at the start of incremental OTA installation (after
2486 verification is complete)."""
2487 return self._DoCall("IncrementalOTA_InstallBegin")
2488
Yifan Hong10c530d2018-12-27 17:34:18 -08002489 def IncrementalOTA_GetBlockDifferences(self):
2490 """Called during incremental OTA installation and verification.
2491 Implementation should return a list of BlockDifference objects describing
2492 the update on each additional partitions.
2493 """
2494 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2495
Doug Zongker05d3dea2009-06-22 11:32:31 -07002496 def IncrementalOTA_InstallEnd(self):
2497 """Called at the end of incremental OTA installation; typically
2498 this is used to install the image for the device's baseband
2499 processor."""
2500 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002501
Tao Bao9bc6bb22015-11-09 16:58:28 -08002502 def VerifyOTA_Assertions(self):
2503 return self._DoCall("VerifyOTA_Assertions")
2504
Tao Bao76def242017-11-21 09:25:31 -08002505
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002506class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002507 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002508 self.name = name
2509 self.data = data
2510 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002511 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002512 self.sha1 = sha1(data).hexdigest()
2513
2514 @classmethod
2515 def FromLocalFile(cls, name, diskname):
2516 f = open(diskname, "rb")
2517 data = f.read()
2518 f.close()
2519 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002520
2521 def WriteToTemp(self):
2522 t = tempfile.NamedTemporaryFile()
2523 t.write(self.data)
2524 t.flush()
2525 return t
2526
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002527 def WriteToDir(self, d):
2528 with open(os.path.join(d, self.name), "wb") as fp:
2529 fp.write(self.data)
2530
Geremy Condra36bd3652014-02-06 19:45:10 -08002531 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002532 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002533
Tao Bao76def242017-11-21 09:25:31 -08002534
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002535DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002536 ".gz": "imgdiff",
2537 ".zip": ["imgdiff", "-z"],
2538 ".jar": ["imgdiff", "-z"],
2539 ".apk": ["imgdiff", "-z"],
2540 ".img": "imgdiff",
2541}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002542
Tao Bao76def242017-11-21 09:25:31 -08002543
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002544class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002545 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002546 self.tf = tf
2547 self.sf = sf
2548 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002549 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002550
2551 def ComputePatch(self):
2552 """Compute the patch (as a string of data) needed to turn sf into
2553 tf. Returns the same tuple as GetPatch()."""
2554
2555 tf = self.tf
2556 sf = self.sf
2557
Doug Zongker24cd2802012-08-14 16:36:15 -07002558 if self.diff_program:
2559 diff_program = self.diff_program
2560 else:
2561 ext = os.path.splitext(tf.name)[1]
2562 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002563
2564 ttemp = tf.WriteToTemp()
2565 stemp = sf.WriteToTemp()
2566
2567 ext = os.path.splitext(tf.name)[1]
2568
2569 try:
2570 ptemp = tempfile.NamedTemporaryFile()
2571 if isinstance(diff_program, list):
2572 cmd = copy.copy(diff_program)
2573 else:
2574 cmd = [diff_program]
2575 cmd.append(stemp.name)
2576 cmd.append(ttemp.name)
2577 cmd.append(ptemp.name)
2578 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002579 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002580
Doug Zongkerf8340082014-08-05 10:39:37 -07002581 def run():
2582 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002583 if e:
2584 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002585 th = threading.Thread(target=run)
2586 th.start()
2587 th.join(timeout=300) # 5 mins
2588 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002589 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002590 p.terminate()
2591 th.join(5)
2592 if th.is_alive():
2593 p.kill()
2594 th.join()
2595
Tianjie Xua2a9f992018-01-05 15:15:54 -08002596 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002597 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002598 self.patch = None
2599 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002600 diff = ptemp.read()
2601 finally:
2602 ptemp.close()
2603 stemp.close()
2604 ttemp.close()
2605
2606 self.patch = diff
2607 return self.tf, self.sf, self.patch
2608
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002609 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002610 """Returns a tuple of (target_file, source_file, patch_data).
2611
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002612 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002613 computing the patch failed.
2614 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002615 return self.tf, self.sf, self.patch
2616
2617
2618def ComputeDifferences(diffs):
2619 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002620 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002621
2622 # Do the largest files first, to try and reduce the long-pole effect.
2623 by_size = [(i.tf.size, i) for i in diffs]
2624 by_size.sort(reverse=True)
2625 by_size = [i[1] for i in by_size]
2626
2627 lock = threading.Lock()
2628 diff_iter = iter(by_size) # accessed under lock
2629
2630 def worker():
2631 try:
2632 lock.acquire()
2633 for d in diff_iter:
2634 lock.release()
2635 start = time.time()
2636 d.ComputePatch()
2637 dur = time.time() - start
2638 lock.acquire()
2639
2640 tf, sf, patch = d.GetPatch()
2641 if sf.name == tf.name:
2642 name = tf.name
2643 else:
2644 name = "%s (%s)" % (tf.name, sf.name)
2645 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002646 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002647 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002648 logger.info(
2649 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2650 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002651 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002652 except Exception:
2653 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002654 raise
2655
2656 # start worker threads; wait for them all to finish.
2657 threads = [threading.Thread(target=worker)
2658 for i in range(OPTIONS.worker_threads)]
2659 for th in threads:
2660 th.start()
2661 while threads:
2662 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002663
2664
Dan Albert8b72aef2015-03-23 19:13:21 -07002665class BlockDifference(object):
2666 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002667 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002668 self.tgt = tgt
2669 self.src = src
2670 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002671 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002672 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002673
Tao Baodd2a5892015-03-12 12:32:37 -07002674 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002675 version = max(
2676 int(i) for i in
2677 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002678 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002679 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002680
Tianjie Xu41976c72019-07-03 13:57:01 -07002681 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2682 version=self.version,
2683 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002684 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002685 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002686 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002687 self.touched_src_ranges = b.touched_src_ranges
2688 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002689
Yifan Hong10c530d2018-12-27 17:34:18 -08002690 # On devices with dynamic partitions, for new partitions,
2691 # src is None but OPTIONS.source_info_dict is not.
2692 if OPTIONS.source_info_dict is None:
2693 is_dynamic_build = OPTIONS.info_dict.get(
2694 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002695 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002696 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002697 is_dynamic_build = OPTIONS.source_info_dict.get(
2698 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002699 is_dynamic_source = partition in shlex.split(
2700 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002701
Yifan Hongbb2658d2019-01-25 12:30:58 -08002702 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002703 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2704
Yifan Hongbb2658d2019-01-25 12:30:58 -08002705 # For dynamic partitions builds, check partition list in both source
2706 # and target build because new partitions may be added, and existing
2707 # partitions may be removed.
2708 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2709
Yifan Hong10c530d2018-12-27 17:34:18 -08002710 if is_dynamic:
2711 self.device = 'map_partition("%s")' % partition
2712 else:
2713 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002714 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2715 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002716 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002717 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2718 OPTIONS.source_info_dict)
2719 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002720
Tao Baod8d14be2016-02-04 14:26:02 -08002721 @property
2722 def required_cache(self):
2723 return self._required_cache
2724
Tao Bao76def242017-11-21 09:25:31 -08002725 def WriteScript(self, script, output_zip, progress=None,
2726 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002727 if not self.src:
2728 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002729 script.Print("Patching %s image unconditionally..." % (self.partition,))
2730 else:
2731 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002732
Dan Albert8b72aef2015-03-23 19:13:21 -07002733 if progress:
2734 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002735 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002736
2737 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002738 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002739
Tao Bao9bc6bb22015-11-09 16:58:28 -08002740 def WriteStrictVerifyScript(self, script):
2741 """Verify all the blocks in the care_map, including clobbered blocks.
2742
2743 This differs from the WriteVerifyScript() function: a) it prints different
2744 error messages; b) it doesn't allow half-way updated images to pass the
2745 verification."""
2746
2747 partition = self.partition
2748 script.Print("Verifying %s..." % (partition,))
2749 ranges = self.tgt.care_map
2750 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002751 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002752 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2753 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002754 self.device, ranges_str,
2755 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002756 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002757 script.AppendExtra("")
2758
Tao Baod522bdc2016-04-12 15:53:16 -07002759 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002760 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002761
2762 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002763 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002764 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002765
2766 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002767 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002768 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002769 ranges = self.touched_src_ranges
2770 expected_sha1 = self.touched_src_sha1
2771 else:
2772 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2773 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002774
2775 # No blocks to be checked, skipping.
2776 if not ranges:
2777 return
2778
Tao Bao5ece99d2015-05-12 11:42:31 -07002779 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002780 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002781 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002782 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2783 '"%s.patch.dat")) then' % (
2784 self.device, ranges_str, expected_sha1,
2785 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002786 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002787 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002788
Tianjie Xufc3422a2015-12-15 11:53:59 -08002789 if self.version >= 4:
2790
2791 # Bug: 21124327
2792 # When generating incrementals for the system and vendor partitions in
2793 # version 4 or newer, explicitly check the first block (which contains
2794 # the superblock) of the partition to see if it's what we expect. If
2795 # this check fails, give an explicit log message about the partition
2796 # having been remounted R/W (the most likely explanation).
2797 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002798 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002799
2800 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002801 if partition == "system":
2802 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2803 else:
2804 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002805 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002806 'ifelse (block_image_recover({device}, "{ranges}") && '
2807 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002808 'package_extract_file("{partition}.transfer.list"), '
2809 '"{partition}.new.dat", "{partition}.patch.dat"), '
2810 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002811 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002812 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002813 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002814
Tao Baodd2a5892015-03-12 12:32:37 -07002815 # Abort the OTA update. Note that the incremental OTA cannot be applied
2816 # even if it may match the checksum of the target partition.
2817 # a) If version < 3, operations like move and erase will make changes
2818 # unconditionally and damage the partition.
2819 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002820 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002821 if partition == "system":
2822 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2823 else:
2824 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2825 script.AppendExtra((
2826 'abort("E%d: %s partition has unexpected contents");\n'
2827 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002828
Yifan Hong10c530d2018-12-27 17:34:18 -08002829 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002830 partition = self.partition
2831 script.Print('Verifying the updated %s image...' % (partition,))
2832 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2833 ranges = self.tgt.care_map
2834 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002835 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002836 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002837 self.device, ranges_str,
2838 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002839
2840 # Bug: 20881595
2841 # Verify that extended blocks are really zeroed out.
2842 if self.tgt.extended:
2843 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002844 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002845 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002846 self.device, ranges_str,
2847 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002848 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002849 if partition == "system":
2850 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2851 else:
2852 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002853 script.AppendExtra(
2854 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002855 ' abort("E%d: %s partition has unexpected non-zero contents after '
2856 'OTA update");\n'
2857 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002858 else:
2859 script.Print('Verified the updated %s image.' % (partition,))
2860
Tianjie Xu209db462016-05-24 17:34:52 -07002861 if partition == "system":
2862 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2863 else:
2864 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2865
Tao Bao5fcaaef2015-06-01 13:40:49 -07002866 script.AppendExtra(
2867 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002868 ' abort("E%d: %s partition has unexpected contents after OTA '
2869 'update");\n'
2870 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002871
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002872 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002873 ZipWrite(output_zip,
2874 '{}.transfer.list'.format(self.path),
2875 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002876
Tao Bao76def242017-11-21 09:25:31 -08002877 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2878 # its size. Quailty 9 almost triples the compression time but doesn't
2879 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002880 # zip | brotli(quality 6) | brotli(quality 9)
2881 # compressed_size: 942M | 869M (~8% reduced) | 854M
2882 # compression_time: 75s | 265s | 719s
2883 # decompression_time: 15s | 25s | 25s
2884
2885 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002886 brotli_cmd = ['brotli', '--quality=6',
2887 '--output={}.new.dat.br'.format(self.path),
2888 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002889 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002890 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002891
2892 new_data_name = '{}.new.dat.br'.format(self.partition)
2893 ZipWrite(output_zip,
2894 '{}.new.dat.br'.format(self.path),
2895 new_data_name,
2896 compress_type=zipfile.ZIP_STORED)
2897 else:
2898 new_data_name = '{}.new.dat'.format(self.partition)
2899 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2900
Dan Albert8e0178d2015-01-27 15:53:15 -08002901 ZipWrite(output_zip,
2902 '{}.patch.dat'.format(self.path),
2903 '{}.patch.dat'.format(self.partition),
2904 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002905
Tianjie Xu209db462016-05-24 17:34:52 -07002906 if self.partition == "system":
2907 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2908 else:
2909 code = ErrorCode.VENDOR_UPDATE_FAILURE
2910
Yifan Hong10c530d2018-12-27 17:34:18 -08002911 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002912 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002913 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002914 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002915 device=self.device, partition=self.partition,
2916 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002917 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002918
Kelvin Zhang0876c412020-06-23 15:06:58 -04002919 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002920 data = source.ReadRangeSet(ranges)
2921 ctx = sha1()
2922
2923 for p in data:
2924 ctx.update(p)
2925
2926 return ctx.hexdigest()
2927
Kelvin Zhang0876c412020-06-23 15:06:58 -04002928 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07002929 """Return the hash value for all zero blocks."""
2930 zero_block = '\x00' * 4096
2931 ctx = sha1()
2932 for _ in range(num_blocks):
2933 ctx.update(zero_block)
2934
2935 return ctx.hexdigest()
2936
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002937
Tianjie Xu41976c72019-07-03 13:57:01 -07002938# Expose these two classes to support vendor-specific scripts
2939DataImage = images.DataImage
2940EmptyImage = images.EmptyImage
2941
Tao Bao76def242017-11-21 09:25:31 -08002942
Doug Zongker96a57e72010-09-26 14:57:41 -07002943# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002944PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002945 "ext4": "EMMC",
2946 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002947 "f2fs": "EMMC",
2948 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002949}
Doug Zongker96a57e72010-09-26 14:57:41 -07002950
Kelvin Zhang0876c412020-06-23 15:06:58 -04002951
Yifan Hongbdb32012020-05-07 12:38:53 -07002952def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2953 """
2954 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2955 backwards compatibility. It aborts if the fstab entry has slotselect option
2956 (unless check_no_slot is explicitly set to False).
2957 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002958 fstab = info["fstab"]
2959 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002960 if check_no_slot:
2961 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002962 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002963 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2964 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002965 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002966
2967
Yifan Hongbdb32012020-05-07 12:38:53 -07002968def GetTypeAndDeviceExpr(mount_point, info):
2969 """
2970 Return the filesystem of the partition, and an edify expression that evaluates
2971 to the device at runtime.
2972 """
2973 fstab = info["fstab"]
2974 if fstab:
2975 p = fstab[mount_point]
2976 device_expr = '"%s"' % fstab[mount_point].device
2977 if p.slotselect:
2978 device_expr = 'add_slot_suffix(%s)' % device_expr
2979 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002980 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07002981
2982
2983def GetEntryForDevice(fstab, device):
2984 """
2985 Returns:
2986 The first entry in fstab whose device is the given value.
2987 """
2988 if not fstab:
2989 return None
2990 for mount_point in fstab:
2991 if fstab[mount_point].device == device:
2992 return fstab[mount_point]
2993 return None
2994
Kelvin Zhang0876c412020-06-23 15:06:58 -04002995
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002996def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002997 """Parses and converts a PEM-encoded certificate into DER-encoded.
2998
2999 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3000
3001 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003002 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003003 """
3004 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003005 save = False
3006 for line in data.split("\n"):
3007 if "--END CERTIFICATE--" in line:
3008 break
3009 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003010 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003011 if "--BEGIN CERTIFICATE--" in line:
3012 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003013 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003014 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003015
Tao Bao04e1f012018-02-04 12:13:35 -08003016
3017def ExtractPublicKey(cert):
3018 """Extracts the public key (PEM-encoded) from the given certificate file.
3019
3020 Args:
3021 cert: The certificate filename.
3022
3023 Returns:
3024 The public key string.
3025
3026 Raises:
3027 AssertionError: On non-zero return from 'openssl'.
3028 """
3029 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3030 # While openssl 1.1 writes the key into the given filename followed by '-out',
3031 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3032 # stdout instead.
3033 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3034 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3035 pubkey, stderrdata = proc.communicate()
3036 assert proc.returncode == 0, \
3037 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3038 return pubkey
3039
3040
Tao Bao1ac886e2019-06-26 11:58:22 -07003041def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003042 """Extracts the AVB public key from the given public or private key.
3043
3044 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003045 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003046 key: The input key file, which should be PEM-encoded public or private key.
3047
3048 Returns:
3049 The path to the extracted AVB public key file.
3050 """
3051 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3052 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003053 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003054 return output
3055
3056
Doug Zongker412c02f2014-02-13 10:58:24 -08003057def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3058 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003059 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003060
Tao Bao6d5d6232018-03-09 17:04:42 -08003061 Most of the space in the boot and recovery images is just the kernel, which is
3062 identical for the two, so the resulting patch should be efficient. Add it to
3063 the output zip, along with a shell script that is run from init.rc on first
3064 boot to actually do the patching and install the new recovery image.
3065
3066 Args:
3067 input_dir: The top-level input directory of the target-files.zip.
3068 output_sink: The callback function that writes the result.
3069 recovery_img: File object for the recovery image.
3070 boot_img: File objects for the boot image.
3071 info_dict: A dict returned by common.LoadInfoDict() on the input
3072 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003073 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003074 if info_dict is None:
3075 info_dict = OPTIONS.info_dict
3076
Tao Bao6d5d6232018-03-09 17:04:42 -08003077 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003078 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3079
3080 if board_uses_vendorimage:
3081 # In this case, the output sink is rooted at VENDOR
3082 recovery_img_path = "etc/recovery.img"
3083 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3084 sh_dir = "bin"
3085 else:
3086 # In this case the output sink is rooted at SYSTEM
3087 recovery_img_path = "vendor/etc/recovery.img"
3088 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3089 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003090
Tao Baof2cffbd2015-07-22 12:33:18 -07003091 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003092 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003093
3094 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003095 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003096 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003097 # With system-root-image, boot and recovery images will have mismatching
3098 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3099 # to handle such a case.
3100 if system_root_image:
3101 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003102 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003103 assert not os.path.exists(path)
3104 else:
3105 diff_program = ["imgdiff"]
3106 if os.path.exists(path):
3107 diff_program.append("-b")
3108 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003109 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003110 else:
3111 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003112
3113 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3114 _, _, patch = d.ComputePatch()
3115 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003116
Dan Albertebb19aa2015-03-27 19:11:53 -07003117 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003118 # The following GetTypeAndDevice()s need to use the path in the target
3119 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003120 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3121 check_no_slot=False)
3122 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3123 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003124 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003125 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003126
Tao Baof2cffbd2015-07-22 12:33:18 -07003127 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003128
3129 # Note that we use /vendor to refer to the recovery resources. This will
3130 # work for a separate vendor partition mounted at /vendor or a
3131 # /system/vendor subdirectory on the system partition, for which init will
3132 # create a symlink from /vendor to /system/vendor.
3133
3134 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003135if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3136 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003137 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003138 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3139 log -t recovery "Installing new recovery image: succeeded" || \\
3140 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003141else
3142 log -t recovery "Recovery image already installed"
3143fi
3144""" % {'type': recovery_type,
3145 'device': recovery_device,
3146 'sha1': recovery_img.sha1,
3147 'size': recovery_img.size}
3148 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003149 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003150if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3151 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003152 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003153 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3154 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3155 log -t recovery "Installing new recovery image: succeeded" || \\
3156 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003157else
3158 log -t recovery "Recovery image already installed"
3159fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003160""" % {'boot_size': boot_img.size,
3161 'boot_sha1': boot_img.sha1,
3162 'recovery_size': recovery_img.size,
3163 'recovery_sha1': recovery_img.sha1,
3164 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003165 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3166 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003167 'recovery_device': recovery_device,
3168 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003169
Bill Peckhame868aec2019-09-17 17:06:47 -07003170 # The install script location moved from /system/etc to /system/bin in the L
3171 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3172 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003173
Tao Bao32fcdab2018-10-12 10:30:39 -07003174 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003175
Tao Baoda30cfa2017-12-01 16:19:46 -08003176 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003177
3178
3179class DynamicPartitionUpdate(object):
3180 def __init__(self, src_group=None, tgt_group=None, progress=None,
3181 block_difference=None):
3182 self.src_group = src_group
3183 self.tgt_group = tgt_group
3184 self.progress = progress
3185 self.block_difference = block_difference
3186
3187 @property
3188 def src_size(self):
3189 if not self.block_difference:
3190 return 0
3191 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3192
3193 @property
3194 def tgt_size(self):
3195 if not self.block_difference:
3196 return 0
3197 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3198
3199 @staticmethod
3200 def _GetSparseImageSize(img):
3201 if not img:
3202 return 0
3203 return img.blocksize * img.total_blocks
3204
3205
3206class DynamicGroupUpdate(object):
3207 def __init__(self, src_size=None, tgt_size=None):
3208 # None: group does not exist. 0: no size limits.
3209 self.src_size = src_size
3210 self.tgt_size = tgt_size
3211
3212
3213class DynamicPartitionsDifference(object):
3214 def __init__(self, info_dict, block_diffs, progress_dict=None,
3215 source_info_dict=None):
3216 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003217 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003218
3219 self._remove_all_before_apply = False
3220 if source_info_dict is None:
3221 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003222 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003223
Tao Baof1113e92019-06-18 12:10:14 -07003224 block_diff_dict = collections.OrderedDict(
3225 [(e.partition, e) for e in block_diffs])
3226
Yifan Hong10c530d2018-12-27 17:34:18 -08003227 assert len(block_diff_dict) == len(block_diffs), \
3228 "Duplicated BlockDifference object for {}".format(
3229 [partition for partition, count in
3230 collections.Counter(e.partition for e in block_diffs).items()
3231 if count > 1])
3232
Yifan Hong79997e52019-01-23 16:56:19 -08003233 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003234
3235 for p, block_diff in block_diff_dict.items():
3236 self._partition_updates[p] = DynamicPartitionUpdate()
3237 self._partition_updates[p].block_difference = block_diff
3238
3239 for p, progress in progress_dict.items():
3240 if p in self._partition_updates:
3241 self._partition_updates[p].progress = progress
3242
3243 tgt_groups = shlex.split(info_dict.get(
3244 "super_partition_groups", "").strip())
3245 src_groups = shlex.split(source_info_dict.get(
3246 "super_partition_groups", "").strip())
3247
3248 for g in tgt_groups:
3249 for p in shlex.split(info_dict.get(
3250 "super_%s_partition_list" % g, "").strip()):
3251 assert p in self._partition_updates, \
3252 "{} is in target super_{}_partition_list but no BlockDifference " \
3253 "object is provided.".format(p, g)
3254 self._partition_updates[p].tgt_group = g
3255
3256 for g in src_groups:
3257 for p in shlex.split(source_info_dict.get(
3258 "super_%s_partition_list" % g, "").strip()):
3259 assert p in self._partition_updates, \
3260 "{} is in source super_{}_partition_list but no BlockDifference " \
3261 "object is provided.".format(p, g)
3262 self._partition_updates[p].src_group = g
3263
Yifan Hong45433e42019-01-18 13:55:25 -08003264 target_dynamic_partitions = set(shlex.split(info_dict.get(
3265 "dynamic_partition_list", "").strip()))
3266 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3267 if u.tgt_size)
3268 assert block_diffs_with_target == target_dynamic_partitions, \
3269 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3270 list(target_dynamic_partitions), list(block_diffs_with_target))
3271
3272 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3273 "dynamic_partition_list", "").strip()))
3274 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3275 if u.src_size)
3276 assert block_diffs_with_source == source_dynamic_partitions, \
3277 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3278 list(source_dynamic_partitions), list(block_diffs_with_source))
3279
Yifan Hong10c530d2018-12-27 17:34:18 -08003280 if self._partition_updates:
3281 logger.info("Updating dynamic partitions %s",
3282 self._partition_updates.keys())
3283
Yifan Hong79997e52019-01-23 16:56:19 -08003284 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003285
3286 for g in tgt_groups:
3287 self._group_updates[g] = DynamicGroupUpdate()
3288 self._group_updates[g].tgt_size = int(info_dict.get(
3289 "super_%s_group_size" % g, "0").strip())
3290
3291 for g in src_groups:
3292 if g not in self._group_updates:
3293 self._group_updates[g] = DynamicGroupUpdate()
3294 self._group_updates[g].src_size = int(source_info_dict.get(
3295 "super_%s_group_size" % g, "0").strip())
3296
3297 self._Compute()
3298
3299 def WriteScript(self, script, output_zip, write_verify_script=False):
3300 script.Comment('--- Start patching dynamic partitions ---')
3301 for p, u in self._partition_updates.items():
3302 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3303 script.Comment('Patch partition %s' % p)
3304 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3305 write_verify_script=False)
3306
3307 op_list_path = MakeTempFile()
3308 with open(op_list_path, 'w') as f:
3309 for line in self._op_list:
3310 f.write('{}\n'.format(line))
3311
3312 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3313
3314 script.Comment('Update dynamic partition metadata')
3315 script.AppendExtra('assert(update_dynamic_partitions('
3316 'package_extract_file("dynamic_partitions_op_list")));')
3317
3318 if write_verify_script:
3319 for p, u in self._partition_updates.items():
3320 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3321 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003322 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003323
3324 for p, u in self._partition_updates.items():
3325 if u.tgt_size and u.src_size <= u.tgt_size:
3326 script.Comment('Patch partition %s' % p)
3327 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3328 write_verify_script=write_verify_script)
3329 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003330 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003331
3332 script.Comment('--- End patching dynamic partitions ---')
3333
3334 def _Compute(self):
3335 self._op_list = list()
3336
3337 def append(line):
3338 self._op_list.append(line)
3339
3340 def comment(line):
3341 self._op_list.append("# %s" % line)
3342
3343 if self._remove_all_before_apply:
3344 comment('Remove all existing dynamic partitions and groups before '
3345 'applying full OTA')
3346 append('remove_all_groups')
3347
3348 for p, u in self._partition_updates.items():
3349 if u.src_group and not u.tgt_group:
3350 append('remove %s' % p)
3351
3352 for p, u in self._partition_updates.items():
3353 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3354 comment('Move partition %s from %s to default' % (p, u.src_group))
3355 append('move %s default' % p)
3356
3357 for p, u in self._partition_updates.items():
3358 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3359 comment('Shrink partition %s from %d to %d' %
3360 (p, u.src_size, u.tgt_size))
3361 append('resize %s %s' % (p, u.tgt_size))
3362
3363 for g, u in self._group_updates.items():
3364 if u.src_size is not None and u.tgt_size is None:
3365 append('remove_group %s' % g)
3366 if (u.src_size is not None and u.tgt_size is not None and
3367 u.src_size > u.tgt_size):
3368 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3369 append('resize_group %s %d' % (g, u.tgt_size))
3370
3371 for g, u in self._group_updates.items():
3372 if u.src_size is None and u.tgt_size is not None:
3373 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3374 append('add_group %s %d' % (g, u.tgt_size))
3375 if (u.src_size is not None and u.tgt_size is not None and
3376 u.src_size < u.tgt_size):
3377 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3378 append('resize_group %s %d' % (g, u.tgt_size))
3379
3380 for p, u in self._partition_updates.items():
3381 if u.tgt_group and not u.src_group:
3382 comment('Add partition %s to group %s' % (p, u.tgt_group))
3383 append('add %s %s' % (p, u.tgt_group))
3384
3385 for p, u in self._partition_updates.items():
3386 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003387 comment('Grow partition %s from %d to %d' %
3388 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003389 append('resize %s %d' % (p, u.tgt_size))
3390
3391 for p, u in self._partition_updates.items():
3392 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3393 comment('Move partition %s from default to %s' %
3394 (p, u.tgt_group))
3395 append('move %s %s' % (p, u.tgt_group))